Postrust vs PostgREST

PostgREST is the project that established this idea: point a server at a PostgreSQL schema and get a REST API, with permissions left to the database. Postrust follows its URL grammar deliberately, so `?select=`, `?order=`, the filter operators and the `Prefer` headers mean the same thing in both.

The differences are not in the query language. They are in what ships in the process and what the deployment looks like.

PostgREST is written in
Haskell
Licence
MIT
Version compared
v16.1
Their site
postgrest.org

Feature comparison

Where a capability has a condition attached, the condition is in the cell.

FeaturePostrustPostgREST
REST API from the schemaYesYes
Query grammarPostgREST-compatibleThe reference
GraphQLBuilt in, with the admin-ui build featureNot supported
SubscriptionsGraphQL subscriptions over LISTEN/NOTIFYNot supported
Custom endpointsAxum handlers in the same binary, needs a rebuildSQL functions via /rpc
Business logicSQL functions, or Rust in-processSQL functions
RuntimeSingle static binarySingle binary
AWS LambdaNative, via the postrust-lambda crateContainer images
LicenseMITMIT
MaturityYoung. Smaller surface, fewer users.Years of production use, large community

Measured performance

Produced by scripts/bench-compare.sh, which starts both servers as containers against the same database and runs the same load against each. Where PostgREST is faster, that is what the table says.

REST

ScenarioPostrust req/sPostgREST req/sPostrust p95PostgREST p95Difference
point lookup14,4794,3234.7 ms31.1 ms3.3× faster
25-row page13,8004,1524.4 ms34.2 ms3.3× faster
filtered + ordered page11,2927,3925.2 ms10 ms1.5× faster
range filter on numeric10,8927,8515.5 ms10.9 ms1.4× faster
25-row page + embed9,4886,0055.9 ms12.2 ms1.6× faster

Image size and memory

PostrustPostgREST
Container image, on disk168MB26.8MB
Memory, before serving a request1.9 MB18.3 MB
Memory, after the benchmark14.8 MB58.0 MB

Memory is resident set size, read the same way for every tool. Image sizes are for the Debian-based build these figures come from. The Alpine build of the same code is 31.7MB.

How this was measured

  • Every server runs as a container on one docker network against the same PostgreSQL instance and the same dataset. No tool gets to skip container overhead.
  • Each tool keeps its own default connection pool and worker settings. Tuning one and not the others measures the tuning, not the tool.
  • Requests are expressed in each tool's own dialect, because the dialects differ. The work asked of PostgreSQL is the same.
  • Every target is warmed before any of them is measured, and the database cache is populated first, so no tool pays to warm the cache for the ones measured after it.
  • Each figure is the median of several runs rather than the best of them, because a best-of-N reports whichever tool got the quietest moment on the machine.
  • These are single-machine numbers from a laptop. They are useful for comparing the tools against each other, not for capacity planning.

Darwin 25.2.0 arm64 · PostgreSQL postgres:16 · bench_items 100000 rows, bench_reviews 300000 rows · 3000 requests at concurrency 50, median of 5 runs after 500 warm-up requests. Full benchmark method

When PostgREST is the better choice

  • You need the full PostgREST surface. Postrust implements the parts of it that are exercised by its test suite, and PostgREST has had years to grow behaviours that Postrust has not reimplemented.
  • You want the answer to an obscure question to already exist. PostgREST's documentation and issue history cover ground a young project has not.
  • REST is all you need, and a second API surface is weight rather than value.
  • You would rather run software that many organisations have already run in production.

When Postrust fits better

  • You want REST and GraphQL from one process rather than two deployments.
  • You are deploying to Lambda and cold start matters.
  • You want to add an endpoint in Rust next to the generated API instead of pushing everything into SQL.

Moving from PostgREST

  • The URL grammar is the same, so existing query strings generally work unchanged.
  • Configuration uses the same `PGRST_*` environment variable names, including `PGRST_DB_ANON_ROLE` and `PGRST_JWT_SECRET`.
  • Tables are mounted under `/api` by default rather than at the root. Check your base URL before assuming a 404 is something worse.
  • NUMERIC columns come back as JSON numbers, as they do from PostgREST, though the scale can differ: 4.2000 where PostgREST gives 4.20. Both parse to the same value.
  • Object keys come back alphabetically rather than in select order. Build with the compat-key-order feature to match PostgREST; it is off by default because it costs up to 15% on wide rows.
  • Verify the specific PostgREST features you depend on against Postrust's test suite before switching anything that matters.

Questions

Is Postrust a drop-in replacement for PostgREST?

For the query grammar and configuration, largely yes. For the whole feature surface, no. PostgREST has been developed for years and Postrust implements a subset. Test the endpoints you actually use.

Why does the same request need a different URL?

Postrust mounts generated routes under /api so custom routes and the admin UI can live alongside them without colliding with a table name.

Does Postrust support PostgREST's /rpc functions?

Yes. Functions in the exposed schema are callable, and that is also how vector similarity search works: the ordering happens inside a SQL function rather than through a query parameter.

Try it against your own schema

Point it at a database and see what it generates. That is a shorter path than reading a comparison table.