quick.sql
A real relational database at your page — tables, joins, constraints — with zero provisioning. It's managed Postgres, made invisible.
quick.db (documents) is the default for “just store some JSON.” quick.sql is the upgrade for when you actually have relations — a leaderboard, a kanban, anything that outgrows a document store around hour three. You get Postgres directly, behind one tagged-template call. No connection string, no setup.
Query from your page
quick.sql is a tagged template that returns the rows. Interpolated values are always bound parameters ($1, $2, …) — never spliced into the SQL — so a hostile value can never inject:
<script src="/quick.js"></script>
<script>
const top = await quick.sql`
select name, sum(points) as pts
from scores group by name order by pts desc limit 10`;
const mine = await quick.sql`select * from scores where name = ${user}`;
</script>
The browser path is read-only: a single select / with statement, automatically row-capped and time-bounded. Reads follow your page's audience — a private page's data stays private. Writes and schema changes are trusted (next section).
Declare your schema
Schema is declared, not improvised, so it's reproducible and travels with the app. Put ordered *.sql files in a sql/ folder next to your page; they're applied at publish, in filename order, exactly once each:
my-app/
index.html
sql/
001_init.sql
002_add_index.sql
-- sql/001_init.sql
create table if not exists scores (
id serial primary key,
name text not null,
points int not null default 0,
created_at timestamptz not null default now()
);
Publish with quickish (or from your dashboard) and the migrations run automatically. A re-publish is a no-op unless you add a new file — applied migrations are tracked, so existing ones never re-run. The sql/ folder is server-side only and never shipped to the browser.
create table, add indexes, and seed rows that a public visitor's read query never could. Visitors can't drop or insert from the page.Writes
Today, writes come from your migrations (DDL + seed data at deploy). To write at runtime — record a score, append a row — do it from a function, your trusted server-side code, so a public visitor can read the leaderboard but only your logic can change it. Direct browser insert/update from the page is intentionally not allowed.
How it's isolated
Each site gets its own Postgres schema, owned by a dedicated least-privilege role that is a member of nothing and not a superuser. A connection for your site can only ever reach your schema — cross-site access is impossible by construction, not by a filter we hope holds. Reads run in a read-only, time-bounded transaction with bound parameters.
It's plain Postgres — use the types, constraints, and functions you already know. Your data round-trips with quickish export. (Direct external psql/ORM connections, replicas, and cross-site joins are out of scope for now.)
quick.db or quick.sql?
| Reach for | When |
|---|---|
quick.db | You just want to store and read some JSON, with live updates and per-page permissions — no schema to think about. |
quick.sql | You have real relations: joins, aggregates, constraints, a leaderboard or kanban that needs group by and order by. |
They're independent — use either, or both, on the same page.
scores table and a top-10 query” — LLMs write correct SQL with near-zero prompting, so this is the most fluent data API to build against.