How fast is an AI-built backend? jerrycan vs FastAPI vs Supabase, measured

We ran the same multi-tenant CRUD API on jerrycan, FastAPI, and Supabase's PostgREST against one Postgres, and load-tested all three. jerrycan is 1.4–1.6× faster than FastAPI and 4–7× faster than PostgREST — with 3–20× lower tail latency, for ~16 lines of hand-written code. Here's the honest, reproducible data.

An agent designs the backend and jerrycan generates it. The obvious question is: is the result actually fast, or just easy to make? So we built the same API three ways — jerrycan, FastAPI, and Supabase’s PostgREST — pointed all three at one Postgres, and put a load generator on them. Everything below is reproducible; the harness and seed are committed.

The short version: jerrycan won every endpoint at every concurrency level we tested, and it’s the one you write the least code for. But a benchmark is only worth anything if it’s fair, so let’s be precise about what “fair” meant here.

What we measured

One canonical workload, chosen because it’s what jerrycan is for, not a hello-world that proves nothing: a multi-tenant CRUD APIorgs → projects → tasks, JWT auth, every read and write scoped to the caller’s organization. This is exactly the code FastAPI and PostgREST make you write (or configure) by hand and that jerrycan generates.

  • Same schema, same data, same database. One Postgres 16, standard config: 100 orgs, 1,000 projects, 20,000 tasks, deterministic seed. All three apps query the identical tables.
  • Same auth. HS256 JWT; each framework scopes idiomatically — jerrycan’s generated Dep<Tenant> guard + scoped repos, FastAPI’s WHERE org_id = ANY(...) after a membership lookup, PostgREST’s RLS policies keyed on the JWT claim.
  • Idiomatic deployment for each. jerrycan: one compiled Rust process (Tokio, all cores), pool 5. FastAPI: 12 uvicorn workers (one per core — Python’s GIL forces multi-process), asyncpg. PostgREST: the official binary, pool 16.
  • The load. oha, warm database, 10 sustained runs per endpoint at fixed concurrency, reporting the median RPS and p50/p90/p99. Any run under 99% success was thrown out as invalid; none were.

The endpoints: list a caller-org’s projects (R1), list its 200 tasks (R2), fetch one task (R3), and update one (W2). (Create is discussed separately — see the findings; jerrycan requires a client-supplied primary key, which is both a finding and a reason it can’t be fixed-body load-tested head-to-head.)

Throughput

Median requests/sec — concurrency 50 (higher is better) 1524 3048 4572 6096 5842 3735 940 R1 list projects 4117 3091 660 R2 list 200 tasks 6096 4205 921 R3 get task 2536 1516 528 W2 update jerrycan FastAPI PostgREST
Median requests/sec, concurrency 50, 10 runs each. jerrycan leads every endpoint.

At concurrency 50, jerrycan is ~1.4–1.6× faster than FastAPI and ~4–7× faster than PostgREST across the board. PostgREST is the surprise loser here — its RLS policy runs a membership sub-query on every request, and that per-request cost dominates for this scoping shape.

Does it scale? (and does jerrycan’s pool of 5 bite?)

jerrycan runs a single process with a hardcoded pool of 5 database connections — versus FastAPI’s 84 (12 workers × 7) and PostgREST’s 16. We expected that to be jerrycan’s ceiling under load. It wasn’t.

Throughput vs concurrency — GET /projects (flat = saturated) 1482 2964 4446 5928 c=50 c=100 c=200 jerrycan 5928 FastAPI 3902 PostgREST 862
Throughput holds flat from concurrency 50 to 200 — the servers are saturated, not falling over. jerrycan stays on top.

From concurrency 50 to 200, jerrycan’s throughput is flat at ~5,900 rps and stays highest. It does more work with 5 connections than FastAPI does with 84 or PostgREST with 16 — because the per-request path is compiled Rust with near-zero framework overhead. As concurrency rises, latency grows linearly (the server is at its throughput ceiling), but jerrycan’s tail stays far lower than the others’:

p99 latency (ms)jerrycanFastAPIPostgREST
R1 · c5012.925.9180.4
R2 · c5017.730.0256.8
W2 · c5026.1135.4332.8
R1 · c20040.2150.1885.2

The other axis: how much did we write?

Speed is half the story. The other half is that the jerrycan version was ~16 lines of hand-written code. Each handler is a single call into a generated, tenant-scoped repository:

Ok(Json(repo.all_for_memberships(user.0.id).await?))

Everything else — the models, DTOs, the Dep<Tenant> membership guard, migrations, routing, dependency injection, and the scoped repo methods (1,260 lines of Rust) — was generated from a design.json an agent wrote. The FastAPI version was ~180 lines of hand-written async code and query-scoping; PostgREST needed hand-written RLS policies and role grants. jerrycan is the fastest and the least code.

Bonus: we migrated a real Supabase app

Since PostgREST is Supabase’s engine, we also tested jerrycan’s Supabase migrator on a real project — the official supabase/supabase Slack-clone example (five related tables, native enums, RLS, plpgsql, realtime, auth.users). jerrycan migrate --from supabase translated it, and the headline worked end-to-end:

Migrated users logged in with their preserved bcrypt passwords (carol@, alice@ → 200 + a JWT, and jerrycan transparently upgraded the hash to argon2 on login), and GET /channels with the migrated token returned the seeded channels.

That’s the promise — migrate, your users keep their passwords, it runs. It is not push-button — the migration flagged 22 gap items (9 blocking) to resolve by hand — but the credential-preserving core works today.

Caveats (read these)

Single developer machine, one workload, one point in the design space. These are relative numbers between three frameworks on identical hardware and data — not datacenter absolutes, and not a claim about your workload. A workload with slower queries would narrow the gap (and would be where jerrycan’s fixed pool of 5 could start to bite). Versions and the full harness — the seed, the oha config, every raw number — are committed so you can re-run it and disagree.

The takeaway

On the exact thing jerrycan is for — multi-tenant CRUD an agent can generate — it was the fastest of the three, at every concurrency we threw at it, for the least hand-written code. That’s the deal: you describe it, an agent builds it, and it’s genuinely fast — with numbers you can re-run yourself.

try the quickstart → more posts