neuron-explorer
Steering-first neuron explorer for Qwen3.5-4B: push every MLP neuron, keep every rollout, browse the results
One Rust binary serving a PostgreSQL schema over REST and GraphQL, with a native AWS Lambda adapter. Measured against PostgREST's and Hasura's own test suites.
Developer toolsRust · TypeScriptGitHub @postrustOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
A PostgREST-inspired REST/GraphQL API for PostgreSQL, written in Rust
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A PostgREST-inspired REST API for PostgreSQL, written in Rust
Postrust is one Rust binary that serves a PostgreSQL schema as a REST API in PostgREST's dialect and a GraphQL API in Hasura's, with an opt-in Apollo Federation v2 subgraph mode. Filtering, pagination, embedding, full CRUD, JWT and row-level security come from the schema; nothing is written per table. It runs as a server, in a container, on AWS Lambda, or -- the REST API -- in a Cloudflare Worker.Read the full README ↗A PostgREST-inspired REST API for PostgreSQL, written in Rust
Features • Quick Start • Documentation • Deployment • Contributing
Postrust is one Rust binary that serves a PostgreSQL schema as a REST API in PostgREST's dialect and a GraphQL API in Hasura's, with an opt-in Apollo Federation v2 subgraph mode. Filtering, pagination, embedding, full CRUD, JWT and row-level security come from the schema; nothing is written per table. It runs as a server, in a container, on AWS Lambda, or -- the REST API -- in a Cloudflare Worker.
Why Postrust?
| Feature | Status | Description |
|---|---|---|
| CRUD Operations | ✅ | GET, POST, PATCH, PUT, DELETE on tables/views |
| Filtering | ✅ | eq, neq, gt, lt, gte, lte, like, ilike, in, is |
| Full-Text Search | ✅ | fts, plfts, phfts, wfts operators |
| Ordering | ✅ | order=column.asc, order=column.desc.nullsfirst |
| Pagination | ✅ | limit, offset, and Range headers (0-9, 100-149, 100-) |
| Column Selection | ✅ | select=col1,col2,relation(nested) |
| Resource Embedding | ✅ | Nested resources via foreign keys |
| RPC Functions | ✅ | Call stored procedures via /api/rpc/function_name (/rpc/... in compatibility mode) |
| JWT Authentication | ✅ | Role-based access with PostgreSQL RLS |
| Content Negotiation | ✅ | JSON, CSV, GeoJSON responses |
| GraphQL API | ✅ | Hasura-dialect GraphQL at /v1/graphql, measured against Hasura's own corpus |
| Platform | Status | Description |
|---|---|---|
| HTTP Server | ✅ | Standalone Axum-based server |
| AWS Lambda | ✅ | The server's router behind the Lambda runtime: REST, GraphQL, JWT and RLS (no subscriptions) |
| Cloudflare Workers | ✅ | The REST API over Hyperdrive, schema cache in KV, JWT and RLS (no GraphQL) — docs/workers.md |
| Feature | Status | Description |
|---|---|---|
| Admin UI | ✅ | Dashboard at /admin (requires admin-ui feature) |
| OpenAPI Spec | ✅ | OpenAPI 3.0 specification at /admin/openapi.json |
| Swagger UI | ✅ | Interactive API docs at /admin/swagger |
| Scalar | ✅ | Modern API docs at /admin/scalar |
| GraphQL Playground | ✅ | Interactive GraphQL IDE at /admin/graphql |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh)# Clone the repository
git clone https://github.com/postrust/postrust.git
cd postrust
# Build the project
cargo build --release
# Binary is at target/release/postrust
# Start PostgreSQL and Postrust
docker-compose up -d
# API is available at http://localhost:3000, mounted under /api by default
curl http://localhost:3000/api/users
The REST surface is served under /api unless
compatibility mode is enabled, which moves it to
the root. The examples further down use root-level paths and assume that mode.
Postrust is configured via environment variables:
# Required
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
# Optional
PGRST_DB_SCHEMAS=public,api # Exposed schemas (default: public)
PGRST_DB_ANON_ROLE=web_anon # Anonymous role
PGRST_JWT_SECRET=your-secret-key # JWT secret for authentication
PGRST_JWT_SECRET_IS_BASE64=false # Set true if secret is base64 encoded
PGRST_SERVER_PORT=3000 # Server port (default: 3000)
PGRST_SERVER_HOST=0.0.0.0 # Server host (default: 127.0.0.1)
PGRST_LOG_LEVEL=info # Log level: error, warn, info, debug
PGRST_COMPAT_MODE=false # PostgREST compatibility mode (default: false)
Note on paths. By default the REST API is served under
/api(e.g.GET /api/users,POST /api/rpc/my_func), GraphQL under/v1/graphqland/api/graphql, alongside the admin UI at/admin. The examples below use root-level paths (/users,/rpc/my_func) — these work as-is when compatibility mode is enabled; otherwise prefix them with/api. See Differences from PostgREST.
# Get all users
curl http://localhost:3000/users
# Get user by ID
curl "http://localhost:3000/users?id=eq.1"
# Create a user
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name": "John", "email": "[email protected]"}'
# Update a user
curl -X PATCH "http://localhost:3000/users?id=eq.1" \
-H "Content-Type: application/json" \
-d '{"name": "Jane"}'
# Delete a user
curl -X DELETE "http://localhost:3000/users?id=eq.1"
# Equality
curl "http://localhost:3000/users?status=eq.active"
# Greater than
curl "http://localhost:3000/orders?amount=gt.100"
# Pattern matching
curl "http://localhost:3000/users?name=like.*john*"
# In list
curl "http://localhost:3000/users?id=in.(1,2,3)"
# Full-text search
curl "http://localhost:3000/articles?title=fts.postgres"
# Combining filters (AND)
curl "http://localhost:3000/users?status=eq.active&role=eq.admin"
# Negation
curl "http://localhost:3000/users?status=not.eq.deleted"
# Order by column
curl "http://localhost:3000/users?order=created_at.desc"
# Multiple ordering
curl "http://localhost:3000/users?order=role.asc,name.desc"
# Pagination
curl "http://localhost:3000/users?limit=10&offset=20"
# Range header -- any window, not only one starting at the first row
curl http://localhost:3000/users -H "Range: 0-9"
curl http://localhost:3000/users -H "Range: 100-149" # -> 206, Content-Range: 100-149/1000
curl http://localhost:3000/users -H "Range: 100-" # row 100 to the end
# What a resource allows, derived from the schema
curl -i -X OPTIONS http://localhost:3000/users
# Allow: OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE
# Embed related resources
curl "http://localhost:3000/orders?select=*,customer(name,email)"
# Nested embedding
curl "http://localhost:3000/orders?select=*,items(product(name,price))"
# Filter on embedded resource
curl "http://localhost:3000/orders?select=*,customer!inner(*)&customer.country=eq.US"
# Call a function
curl -X POST http://localhost:3000/rpc/get_statistics
# With parameters
curl -X POST http://localhost:3000/rpc/search_users \
-H "Content-Type: application/json" \
-d '{"query": "john", "limit": 10}'
# GET for read-only functions
curl "http://localhost:3000/rpc/get_user_count"
Postrust serves a GraphQL API alongside the REST one, in the dialect Hasura speaks — so a client generated against Hasura points at it unchanged:
Apollo Federation v2 subgraph mode is available with
PGRST_GRAPHQL_FEDERATION; see Configuration.
# Query users
curl -X POST http://localhost:3000/v1/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "{ users { id name email } }"
}'
# Filtering, ordering and pagination
curl -X POST http://localhost:3000/v1/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "{ users(where: {status: {_eq: \"active\"}}, order_by: [{name: asc}], limit: 10) { id name } }"
}'
# Nested queries (relationships), and an aggregate of them
curl -X POST http://localhost:3000/v1/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "{ orders { id total customer { name } items_aggregate { aggregate { count } } } }"
}'
# Mutations. Several root fields in one mutation are one transaction.
curl -X POST http://localhost:3000/v1/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation { insert_users(objects: [{name: \"John\", email: \"[email protected]\"}]) { affected_rows returning { id name } } }"
}'
# GraphQL Playground
open http://localhost:3000/v1/graphql
Everything checkable without Hasura runs from one script:
scripts/check.sh # formatting, lints, unit tests
DATABASE_URL=... scripts/check.sh # and the 82 that need a database
Measured against Hasura's own test corpus: see the harness and what it found.
# Request with JWT
curl http://localhost:3000/users \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# JWT payload should include role claim:
# {
# "role": "authenticated_user",
# "sub": "user123",
# "exp": 1234567890
# }
# Return created/updated records
curl -X POST http://localhost:3000/users \
-H "Prefer: return=representation" \
-d '{"name": "John"}'
# Return only headers (for count)
curl http://localhost:3000/users \
-H "Prefer: count=exact"
# Upsert (insert or update)
curl -X POST http://localhost:3000/users \
-H "Prefer: resolution=merge-duplicates" \
-d '{"id": 1, "name": "Updated Name"}'
# Build release binary
cargo build --release -p postrust-server
# Run
DATABASE_URL="postgres://..." ./target/release/postrust
The Admin UI provides OpenAPI documentation, Swagger UI, Scalar, and GraphQL Playground:
# Build with admin-ui feature
cargo build --release -p postrust-server --features admin-ui
# Return object keys in select order, as PostgREST does, rather than
# alphabetically. Off by default: it costs 1% on a three-column response and
# 15% on an eight-column one, because for objects this small a few string
# comparisons beat hashing every key. See docs/configuration.md.
cargo build --release -p postrust-server --features compat-key-order
# Run - Admin UI available at /admin
DATABASE_URL="postgres://..." ./target/release/postrust
# Access admin endpoints:
# - /admin - Dashboard with links to all tools
# - /admin/swagger - Swagger UI for interactive API testing
# - /admin/scalar - Scalar modern API documentation
# - /admin/graphql - GraphQL Playground
# - /admin/openapi.json - Raw OpenAPI 3.0 specification
# Build for Lambda (requires cargo-lambda)
cargo lambda build --release -p postrust-lambda
# Run it in AWS's runtime image against PostgreSQL with RLS
scripts/serverless/lambda.sh
# Deploy: docs/deployment.md has SAM and Serverless Framework templates
Example SAM template:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
PostrustFunction:
Type: AWS::Serverless::Function
Properties:
Handler: bootstrap
Runtime: provided.al2023
CodeUri: target/lambda/postrust-lambda/
MemorySize: 256
Timeout: 30
Environment:
Variables:
DATABASE_URL: !Ref DatabaseUrl
PGRST_JWT_SECRET: !Ref JwtSecret
Events:
Api:
Type: HttpApi
FROM rust:1.88-bookworm AS builder
WORKDIR /app
COPY . .
RUN cargo build --release -p postrust-server
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y libssl3 ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/postrust /usr/local/bin/
EXPOSE 3000
CMD ["postrust"]
┌─────────────────────────────────────────────────────────────┐
│ HTTP Request │
│ REST: /users GraphQL: /graphql │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ postrust-server │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Axum │ │ Lambda │ │ Cloudflare Workers │ │
│ │ Server │ │ Adapter │ │ Adapter │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ postrust-auth │
│ JWT Validation • Role Extraction │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ postrust-core │ │ postrust-graphql │
│ ┌────────────────────────┐ │ │ ┌────────────────────────┐ │
│ │ REST Request Parser │ │ │ │ GraphQL Schema │ │
│ │ • Query params │ │ │ │ • Dynamic types │ │
│ │ • Prefer headers │ │ │ │ • Queries/Mutations │ │
│ └────────────────────────┘ │ │ └────────────────────────┘ │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ ┌────────────────────────┐ │ │ ┌────────────────────────┐ │
│ │ Schema Cache │◄─┼─┼──│ Resolvers │ │
│ │ • Tables, columns │ │ │ │ • Query → ReadPlan │ │
│ │ • Relationships │ │ │ │ • Mutation → Plan │ │
│ │ • Routines │ │ │ └────────────────────────┘ │
│ └────────────────────────┘ │ └──────────────────────────────┘
│ │ │ │
│ ▼ │ │
│ ┌────────────────────────┐ │ │
│ │ Query Planner │ │ │
│ │ • ReadPlan │◄─┼───────────────┘
│ │ • MutatePlan │ │
│ └────────────────────────┘ │
└──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ postrust-sql │
│ Type-safe SQL Builder │
│ Parameterized Queries │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL │
│ Row-Level Security • Roles │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ postrust-response │
│ JSON • CSV • GeoJSON │
└─────────────────────────────────────────────────────────────┘
All crates share the workspace version and are published together, so any two Postrust crates at the same version work together. Browse them at postrust.org/packages.
| Crate | Description | Links |
|---|---|---|
postrust-server | Standalone HTTP server (Axum), ships the postrust binary | crates.io · docs |
postrust-engine | The REST request pipeline, from HTTP request to response, with no web framework | crates.io · docs |
postrust-core | Request parsing, schema cache, query planning | crates.io · docs |
postrust-sql | Type-safe SQL builder with parameterized queries | crates.io · docs |
postrust-auth | JWT authentication and role extraction | crates.io · docs |
postrust-response | Response formatting (JSON, CSV, OpenAPI) | crates.io · docs |
postrust-graphql | GraphQL API with dynamic schema generation | crates.io · |
The first eight crates share one version and one semver promise.
postrust-worker keeps semver on a version of its own, over what a deployment
sees -- its configuration, bindings and endpoints -- rather than a Rust API.
postrust-proxy is on its own 0.x line and carries none: it is useful and
measured — h2spec, Autobahn, and the HTTP Garden differential fuzzer — but
parts of its public surface are undocumented, and a shared version number would
promise a stability it cannot keep.
Nothing in the stable line depends on the proxy. See Stability and Versioning.
# Debug build
cargo build
# Release build
cargo build --release
# Run tests
cargo test
# Run with Docker (includes PostgreSQL)
docker-compose up
# Unit tests
cargo test
# Integration tests (requires PostgreSQL)
docker-compose up -d postgres
DATABASE_URL="postgres://postgres:postgres@localhost:5432/postrust_test" cargo test
# With coverage
cargo tarpaulin --out Html
postrust/
├── Cargo.toml # Workspace manifest
├── docker-compose.yml # Development environment
├── crates/
│ ├── postrust-core/ # Core library
│ │ └── src/
│ │ ├── api_request/ # Request parsing
│ │ ├── schema_cache/ # DB introspection
│ │ ├── plan/ # Query planning
│ │ └── query/ # SQL generation
│ ├── postrust-sql/ # SQL builder
│ ├── postrust-auth/ # Authentication
│ ├── postrust-response/ # Response formatting
│ ├── postrust-graphql/ # GraphQL API
│ │ └── src/
│ │ ├── schema/ # Dynamic schema generation
│ │ ├── resolver/ # Query/mutation resolvers
│ │ ├── input/ # Filter/order/mutation inputs
│ │ └── handler.rs # Axum handler
│ ├── postrust-engine/ # REST request pipeline
│ ├── postrust-server/ # HTTP server
│ ├── postrust-lambda/ # Lambda adapter
│ └── postrust-worker/ # Workers adapter
└── docs/ # Documentation
AMD EPYC 9255, governor=performance smt=off, PostgreSQL 16, 30,000 requests
at concurrency 50, median of 3 runs. The database, the server under test and
the load generator each get their own CCD, and every target gets the identical
core allocation. Figures are the harness's own output, via
website/src/data/measured.ts.
REST, against PostgREST v16.1 (requests/second, and median latency):
| Scenario | Postrust | PostgREST | Ratio |
|---|---|---|---|
| point lookup | 44,324 (1.1 ms) | 10,958 (2.8 ms) | 4.0x |
| 25-row page | 34,633 (1.4 ms) | 10,488 (4.0 ms) | 3.3x |
| filtered + ordered page | 32,701 (1.5 ms) | 6,306 (6.6 ms) | 5.2x |
| range filter on numeric | 30,837 (1.6 ms) | 7,099 (6.1 ms) | 4.3x |
| 25-row page + embed | 20,093 (2.4 ms) | 2,152 (16.8 ms) | 9.3x |
GraphQL, against Hasura v2.50.1 and PostGraphile, sent the same query text byte for byte:
| Scenario | Postrust | Hasura | PostGraphile |
|---|---|---|---|
| single row by primary key | 32,146 | 9,650 (3.3x) | 14,127 (2.3x) |
| 25-row page | 17,411 | 10,379 (1.7x) | 9,003 (1.9x) |
| 25-row page + embed | 10,092 | 8,024 (1.3x) | 4,498 (2.2x) |
Read them as ratios rather than as capacity, and as a comparison of whole designs rather than of HTTP layers.
count(), builds the JSON inside PostgreSQL
and reads three GUCs via current_setting — on every request, whether or not
any of it was asked for. Postrust renders JSON itself. That is a real
difference and a real cost users pay, but it is not evidence that this
server's HTTP layer is four times faster.The first measurement of a run is repeated as the run's last action, and a disagreement beyond 3% fails the run. On the run above the two readings agreed to 0.1%. Every target is also fetched and compared before anything is timed: mismatched row counts fail the run, because unequal work measured precisely is still unequal work.
This matters more than the figures. A deliberately short 2,000-request window
makes the same REST comparison read 13.1x instead of 4.0x, from a run that
looks perfectly healthy. The gate is what stands between a number like that and
this table. An earlier set of figures was withdrawn for exactly that reason;
scripts/BENCH-FINDINGS.md keeps that
investigation, including the eight causes ruled out and the mechanism that was
never identified.
Deterministic, and unaffected by any of the above:
| Resource | Measured |
|---|---|
Binary size (--features admin-ui, as shipped) | 5,220,864 bytes (4.98 MiB) |
| Binary size (default features) | 3,070,080 bytes (2.93 MiB) |
| Image size (Alpine) | 34.9 MB |
| RSS, idle / after the run (Alpine) | 2.8 MB / 21.6 MB |
For comparison on the same host, PostgREST holds 53.5 MB RSS after the run, Hasura 204 MB and PostGraphile 102 MB.
The method is documented in Benchmarking, and the harness runs:
./scripts/bench.sh
Conformance is a separate matter: it measures whether two servers give the same answer, not how fast.
| Feature | Postrust | PostgREST |
|---|---|---|
| Language | Rust | Haskell |
| Binary Size | ~5 MB (~3 MB without admin-ui) | ~20 MB |
| Serverless Support | Native | Via containers |
| Configuration | Env vars | Config file + env |
| OpenAPI | ✅ (admin-ui feature) | ✅ |
| GraphQL | ✅ | ❌ |
| Admin UI | ✅ (Swagger, Scalar) | ❌ |
| Object key order | Alphabetical, or select order with compat-key-order | Select order |
Binary sizes are measured; the rest of this table is a feature comparison rather than a measurement. Throughput is deliberately absent — see Benchmarks above. How closely Postrust matches PostgREST's behaviour is measured, by replaying PostgREST's own test suite against both: see PostgREST Conformance.
Postrust aims to be familiar to PostgREST users, but a few things differ by default. Most are addressed by compatibility mode; the rest are documented here so you don't assume bit-for-bit compatibility.
| Area | PostgREST | Postrust (default) | Compatibility mode |
|---|---|---|---|
| REST base path | / (e.g. POST /rpc/foo) | Nested under /api (e.g. POST /api/rpc/foo) — leaves room for /v1/graphql, /admin on the same server | Also served at / |
| RPC response shape | Bare result: {...} for a single/scalar return, a top-level array for set-returning functions | Array-wrapped, function-name-keyed: [{"foo": {...}}] | Un-wrapped to match PostgREST |
| Config source | Config file and env vars | Environment variables only | — |
Root endpoint / | OpenAPI spec | Small JSON server-info document | Serves neither yet — see below |
| Object key order | Select order | Alphabetical, or select order with the compat-key-order build feature | Unchanged — it is a build-time choice |
Prefer: tx=rollback | Honoured | Not implemented, and not reported as applied | — |
JWT exp / nbf | All checked to the second | exp to the second; 30s clock skew on nbf and iat | — |
The differences that are deliberate — including cases where PostgREST is the one that is wrong — are listed with their evidence in PostgREST conformance.
The largest gap is the OpenAPI document. PostgREST serves Swagger 2.0 at /;
Postrust serves OpenAPI 3.0 for its own surface under /admin, behind the
admin-ui feature, and does not yet generate PostgREST's. Not every PostgREST
config knob is implemented either. Contributions welcome.
Set PGRST_COMPAT_MODE=true (alias: POSTRUST_COMPAT_MODE=true) to make the
API behave more like PostgREST:
/,
so POST /rpc/<name>, GET /<table>, etc. work in addition to the
/api-prefixed paths. Explicit routes (/, /_, /admin, /api) still take
precedence.POST /rpc/<name> are
un-wrapped: a non-set-returning function returns its bare object/scalar
({...} / 42) and a set-returning function returns a top-level array
([{...}, {...}]), instead of the default [{"<name>": ...}] shape.# Default mode
curl -X POST http://localhost:3000/api/rpc/get_statistics
# -> [{"get_statistics": {"users": 10}}]
# Compatibility mode (PGRST_COMPAT_MODE=true)
curl -X POST http://localhost:3000/rpc/get_statistics
# -> {"users": 10}
This is opt-in so existing Postrust deployments keep their current behavior.
How closely the two agree is measured rather than asserted: the harness in
scripts/conformance/ replays PostgREST's own test cases over HTTP against
both servers on identically loaded fixture databases. Over 1499 cases against
PostgREST v16.1, 96.7% agree on status and body, and 94.9% on the full
contract — status, body, and every header that is part of the answer. See
PostgREST conformance for what that covers,
where the two disagree on purpose, and what is still open.
admin-ui feature)Contributions are welcome! Please read our Contributing Guide for details.
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is licensed under the MIT License - see the LICENSE file for details.
Made with ❤️ by the Postrust contributors
postrust-lambda | AWS Lambda adapter | crates.io · docs |
postrust-worker | Cloudflare Workers adapter (REST over Hyperdrive) — 1.x, its own version | crates.io · docs |
postrust-proxy | Reverse proxy with load balancing and automatic TLS — 0.x, no stability promise | guide · crates.io · docs |