Server: PostgREST
PostgREST auto-generates a REST API from a Postgres schema. Twill runs it unmodified — the server convincingly emulates enough Postgres (version probe, catalog reflection, query-template rewriting) that an off-the-shelf PostgREST serves full CRUD plus foreign-key embedding over the engine, with zero engine changes.
How it works (the composition)
This is the most advanced "compose around the engine" example. PostgREST thinks it's talking to Postgres because the server (crates/server) does three things on its behalf:
- Version probe — answers PostgREST's startup checks so it proceeds (
introspect.rs). - Catalog reflection — reflects the engine's tables and foreign-key relationships into the binary catalog format PostgREST loads into its schema cache (
reflect.rs). - Data-path rewriting — rewrites PostgREST's fixed query templates (GET/POST/PATCH/DELETE and FK-embedding reads) into engine-runnable SQL, assembling nested JSON itself with a nested-loop join (
datapath.rs).
So the engine never sees LEFT JOIN LATERAL, row_to_json, or json_agg — the server handles those. The result: relationships reflect into PostgREST's schema cache and embedding (?select=col,rel(col), both many-to-one and one-to-many) works end-to-end.
Step 1 — start the engine server
cargo run -p twill-server -- --listen 127.0.0.1:5433 --db file://./srv.db
Step 2 — create schema with foreign keys
The engine tracks FK metadata (from inline REFERENCES / table-level FOREIGN KEY), which is what PostgREST needs to expose embedding. (It is metadata only — the engine does not enforce referential integrity in this phase.)
CREATE TABLE authors (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE books (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
author_id INTEGER REFERENCES authors(id)
);
Step 3 — point PostgREST at the server
# postgrest.conf
db-uri = "postgresql://[email protected]:5433/main?sslmode=disable"
db-schemas = "public"
db-anon-role = "postgres"
postgrest postgrest.conf # unmodified PostgREST 14.x
Breakdown: the db-uri is the same cleartext DSN every client uses. PostgREST connects, reflects the schema cache (tables + FKs), and starts serving.
Step 4 — use the REST API
# list
curl 'http://localhost:3000/books'
# filter + select columns
curl 'http://localhost:3000/books?title=eq.Dune&select=id,title'
# FK embedding — many-to-one (book → its author)
curl 'http://localhost:3000/books?select=title,authors(name)'
# FK embedding — one-to-many (author → their books)
curl 'http://localhost:3000/authors?select=name,books(title)'
# create
curl -X POST 'http://localhost:3000/books' \
-H 'content-type: application/json' \
-d '{"id":1,"title":"Dune","author_id":1}'
Breakdown: the embedding queries (select=…,rel(col)) are decomposed by the server into per-relation engine queries whose nested JSON it assembles — a nested-loop join in composition glue, invisible to the engine.
Scope
Built against captured PostgREST SQL
The rewrite targets PostgREST 14.x's actual query templates. Standard CRUD and FK embedding are supported; very advanced PostgREST features outside those captured templates may not be. This is composition glue living entirely in crates/server — the engine core is untouched. For the broader composition philosophy see Auth (better-auth) and Analytics (DuckDB).