Which languages are the established PostgreSQL API servers written in?
Haskell, TypeScript on Node.js, Rust and Go. The two longest-established, PostgREST and Hasura v2, are Haskell; PostGraphile is TypeScript; of the newer tools listed here, three are Rust and one is Go.
| Tool | Language / runtime | API surface | How it ships | Licence |
|---|---|---|---|---|
| PostgREST | Haskell | REST | Single binary, or a container image | MIT |
| Hasura v2 | Haskell | GraphQL | Container | Apache 2.0 (v2 core engine) |
| Hasura DDN (v3 engine) | Rust | GraphQL | Engine plus a data connector per source; the PostgreSQL connector is also Rust | Apache 2.0 repository |
| PostGraphile V5 | TypeScript on Node.js | GraphQL | npm package on a Node.js runtime | MIT |
| pg_graphql | Rust, built with pgrx | GraphQL | PostgreSQL extension, runs inside the database | Apache 2.0 |
| GraphJin | Go | GraphQL | Go binary or library | Apache 2.0 |
| Supabase | A platform of services (PostgREST, GoTrue, Realtime) | REST through PostgREST; GraphQL through a Postgres extension | Managed, or self-host the stack | Apache 2.0 |
| Postrust | Rust | REST (PostgREST dialect) and GraphQL (Hasura dialect) | Single static binary, container, AWS Lambda, Cloudflare Workers | MIT |
Versions are the ones the comparison pages describe: PostgREST v16.1, Hasura v2.50.1 and PostGraphile V5. Hasura recommends DDN for new projects, and it is a different architecture from v2 rather than a rewrite of the same server. For how the GraphQL tools differ in features rather than language, see PostgreSQL GraphQL tools compared.
Which PostgreSQL API tools are written in Rust?
Postrust, the Hasura DDN v3 engine and pg_graphql. They use Rust in three quite different shapes.
- Postrust is a standalone server built on Axum and SQLx. One binary serves a REST API in PostgREST’s dialect and a GraphQL API in Hasura’s, and the same code runs as a server, in a container, on AWS Lambda and on Cloudflare Workers.
- Hasura DDN’s v3 engine does not query the database itself. It sends an intermediate query to a data connector, and the PostgreSQL connector (ndc-postgres) is Rust too.
- pg_graphql is not a server at all. It is a PostgreSQL extension built with the pgrx framework, so the GraphQL query is resolved inside the database and something else has to put it on HTTP. On Supabase, that is PostgREST.
Where does the time in an API request actually go?
Mostly into the database: the network round trip to PostgreSQL and the query it runs. The server’s own work — parsing the request, checking a token, building SQL, writing JSON — is usually the smaller part, whatever language it is written in.
Postrust’s Cloudflare Workers documentation measures this directly. With the round trip to the database set exactly, by delaying every packet the database sends, a read from the Postrust server took 1.0 ms with no added delay, 34 ms with 5 ms added, 124 ms with 20 ms and 364 ms with 60 ms. The server did not get slower; it waited on the database once per statement. On Cloudflare through Hyperdrive, a read with the Worker and the database both in Singapore took 41 ms, and 255 ms with the database in Delhi, about 75 to 80 ms away.
So the decisions that move latency most are architectural: how many statements a request needs, whether they are sent together, and what the generated SQL asks PostgreSQL to do. Hasura v2 compiles a GraphQL query into SQL rather than resolving it field by field, and PostGraphile V5’s Grafast engine plans a query once and executes it with far fewer round trips than a naive resolver tree. A server that issues one query where another issues ten will usually win, in any language.
The benchmark page makes the same point about its own results. Postrust and PostgREST each issue exactly one query per request there, but Postrust sends a plain parameterised SELECT and renders the JSON itself, while PostgREST wraps the query in a CTE, computes a count and builds the JSON inside PostgreSQL on every request. Much of the measured difference is where each design does its work, not the speed of the HTTP layer. Connection pooling belongs in the same category: Postrust keeps a pool open (default size 10), and a server that opened a connection per request would pay for it in round trips.
How much memory and disk does each runtime use?
This is where the runtime shows most plainly. Haskell, Node.js and Rust programs carry different amounts of runtime with them, and that is visible in memory use and container image size.
| Server | Container image, on disk | Memory before the first request | Memory after the benchmark |
|---|---|---|---|
| Postrust | 149 MB | 3.0 MB | 17.1 MB |
| PostgREST v16.1 | 27 MB | 5.2 MB | 51.0 MB |
| Hasura v2.50.1 | 602 MB | 166.1 MB | 202.5 MB |
| PostGraphile V5 | 2.29 GB | 54.7 MB | 215.3 MB |
Resident set size, read the same way for every server, from Postrust’s benchmark harness on AMD EPYC 9255 24-Core Processor with postgres:16, Debian-based images. The Alpine build of Postrust is a 34.9 MB image. Image size depends heavily on the base image: PostgREST’s official image is the smallest here.
The Postrust binary itself is 3,070,080 bytes (2.93 MiB) with default features and 5,220,864 bytes (4.98 MiB) with the admin UI, as shipped. A compiled language with no garbage-collected runtime starts small and stays close to what it is actively using; the figures above are one measurement of that, on one host, not a capacity plan.
Does the language matter for AWS Lambda and Cloudflare Workers?
More than anywhere else. Serverless platforms start instances on demand, cap memory and CPU per request, and limit what can be uploaded, so a small self-contained binary is a better fit than a server that needs a language runtime installed and warmed up.
Postrust treats both as first-class deployment targets. On AWS Lambda the function runs the server’s own router, so REST, GraphQL, JWT authentication and row-level security behave identically; the schema cache loads once per sandbox, in the init phase, so warm invocations do not pay for it. On Cloudflare Workers it runs the server’s own REST request pipeline compiled to WebAssembly, reaching PostgreSQL through Hyperdrive. That bundle is 2.7 MB of WebAssembly, 0.94 MB gzipped, within the free plan’s 3 MB.
Cold starts are still real, and again the database dominates them. The first request in a new Worker isolate took about 440 ms with the database in Singapore when the isolate had to read the catalog itself, and about 230 ms with the schema cache in Workers KV; with the database in Delhi, about 2,000 ms against about 400 ms. That first request uses 60 to 250 ms of CPU, mostly starting the WebAssembly, against 4 to 11 ms for later ones.
How do the concurrency models differ?
All of these servers handle many requests at once without a thread per request, so for a database-bound API none of the models is a bottleneck by itself.
Haskell’s GHC runtime schedules lightweight threads across cores. Node.js runs JavaScript on a single-threaded event loop, so CPU-heavy work in one request delays the others unless the deployment runs several processes. Rust servers such as Postrust use async tasks on a multi-threaded runtime (Tokio, under Axum). Go uses goroutines. In every case the useful limit is usually the connection pool and the database behind it, not the scheduler.
How much should the language weigh when choosing a PostgreSQL API server?
Less than features, maturity and fit, unless footprint or serverless is a hard requirement. PostgREST and Hasura have years of production use behind them; PostGraphile is the most customisable; Supabase is a whole platform. Choose on those grounds first.
The language becomes the deciding factor when the deployment shape is: a small container, many replicas, a function that must start quickly, or an edge runtime with a size limit. That is the case Postrust was built for. The PostgREST, Hasura, PostGraphile and Supabase comparisons set out where each of those tools is the better choice.