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.
| Feature | Postrust | PostgREST |
|---|---|---|
| REST API from the schema | Yes | Yes |
| Query grammar | PostgREST-compatible | The reference |
| GraphQL | Built in, with the admin-ui build feature | Not supported |
| Subscriptions | GraphQL subscriptions over LISTEN/NOTIFY | Not supported |
| Custom endpoints | Axum handlers in the same binary, needs a rebuild | SQL functions via /rpc |
| Business logic | SQL functions, or Rust in-process | SQL functions |
| Runtime | Single static binary | Single binary |
| AWS Lambda | Native, via the postrust-lambda crate | Container images |
| License | MIT | MIT |
| Maturity | Young. 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
| Scenario | Postrust req/s | PostgREST req/s | Postrust p95 | PostgREST p95 | Difference |
|---|---|---|---|---|---|
| point lookup | 14,479 | 4,323 | 4.7 ms | 31.1 ms | 3.3× faster |
| 25-row page | 13,800 | 4,152 | 4.4 ms | 34.2 ms | 3.3× faster |
| filtered + ordered page | 11,292 | 7,392 | 5.2 ms | 10 ms | 1.5× faster |
| range filter on numeric | 10,892 | 7,851 | 5.5 ms | 10.9 ms | 1.4× faster |
| 25-row page + embed | 9,488 | 6,005 | 5.9 ms | 12.2 ms | 1.6× faster |
Image size and memory
| Postrust | PostgREST | |
|---|---|---|
| Container image, on disk | 168MB | 26.8MB |
| Memory, before serving a request | 1.9 MB | 18.3 MB |
| Memory, after the benchmark | 14.8 MB | 58.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.