Zero-proxy full stack · SQL + HTML

PostgreSQL is your web server.

An HTTP listener runs inside a Postgres background worker. Your app is .sql handlers and .html templates — no Node, no app tier, no ORM. The database serves the site.

Get started How it works

Browser
   │
   ▼
Caddy (TLS)
   │
   ▼
PostgreSQL
  ├─ HTTP workers   (extension)
  ├─ SQL handlers   (your app)
  └─ your data      (right there)

A route is a folder. This is the whole page.

SQL returns JSON; HTML renders it. Nothing in between.

-- pages/posts/[id].sql
CREATE FUNCTION pgweb.pages__posts__id(req json)
RETURNS json AS $$
  SELECT json_build_object(
    'post', (SELECT row_to_json(p)
             FROM posts p
             WHERE id = (req->'path_params'->>'id')::int)
  );
$$ LANGUAGE sql;
<!-- pages/posts/[id].html -->
<h1>{{ post.title }}</h1>
<article>{{ post.body }}</article>

What falls out of that decision

One container

Postgres, the HTTP server, and your app ship as a single image. Deploy is docker compose up.

Zero hops to data

Handlers run over SPI in the database's own process. No connection pool, no network round-trip per query.

Push in seconds

pg-web push upserts your files into framework tables. The live server picks them up on the next request.

A real dev loop

pg-web dev watches files, pushes on save, and live-reloads the browser.

No ORM layer

SQL is the interface. One transaction per request — commit on success, rollback on error.

An app you can pg_dump

Routes, templates, assets, and data live in one database. Backup and clone with tools you already trust.

From zero to served

cargo install pg-web

pg-web init my-app --template todo
cd my-app
pg-web up
pg-web migrate apply
pg-web push

The full quickstart →

Where this is honest. pg-web is Phase 1 software: the synchronous core is shipped and benchmarked, auth and background jobs are not here yet.

It is not an ORM, not a SPA framework, and it won't run on managed Postgres that blocks custom extensions (RDS et al.). The full story →