Architecture

How it works

The web server is not next to the database. It lives inside it.

The idea

A typical stack runs an app server whose main job is moving data between Postgres and a template engine. pg-web deletes that tier: a Rust extension (built with pgrx) starts HTTP listeners as Postgres background workers. Requests are handled by SQL functions in the same process tree that owns the data.

Browser
   │
   ▼
Caddy (TLS termination — the one thing kept outside)
   │
   ▼
PostgreSQL
  ├─ HTTP background workers  (pg_web_ext, Rust/pgrx)
  ├─ Your SQL handlers        (called via SPI — no TCP hop)
  └─ Your data

The request lifecycle

  1. A worker accepts the request and opens one SPI transaction.
  2. The router matches the path against pgweb.routes (static and [id]-capture routes).
  3. Your handler function runs: (req json) → json. req carries body, query, method, path.
  4. If the route has a template, Tera renders the JSON into HTML; otherwise the body returns as-is.
  5. Clean response → commit. Any error → rollback, and an error page. One request, one transaction — always.

Concurrency without threads

Each worker is single-threaded (SQL execution must stay on its own backend), so pg-web runs K workers behind SO_REUSEPORT and lets the kernel balance connections. A slow handler stalls one worker, not the site: in our head-of-line-blocking benchmark, fast requests behind a deliberately slow one went from p99 ≈ 217 ms with one worker to ≈ 2 ms with four. The harness that produced those numbers lives in the repo (bench/) and runs as a regression gate.

How your files become an app

The extension never touches the filesystem. The pg-web CLI reads your project — pages/, components/, public/, migrations/ — and upserts it into framework tables. The live server sees new rows on the next lookup, so a push deploys in seconds with no restart. You never compile Rust.

Honest non-goals

Get started Read the docs