Nuxt 4's App Router layout with a multi-tenant API and typed server routes.
server/utils/db.tsas the canonical DB handle. Nitro auto-imports everything underserver/utils/, so route handlers calldbFor(tid)without an explicit import. The Pool is cached onglobalThisso HMR doesn't duplicate it in dev — same trick the Next.js example uses, different framework.server/plugins/close-pool.tsreleases the Pool on shutdown / HMR. Without it, every dev-server reload (and every production graceful shutdown) orphans the old module's Pool rather than closing it — Postgres eventually refuses new connections with too many clients. The plugin hooks Nitro'scloseevent and ends the shared Pool.- File-system-routed method handlers.
tasks.get.ts,tasks.post.ts,tasks/[id].patch.ts— Nitro picks the method up from the suffix, noif (event.method === ...)boilerplate. Each handler gets a fully typed event. - AbortSignal pass-through. The PATCH handler grabs
event.node.req.signaland forwards it to sumak'sexec({ signal })— a client disconnect cancels the UPDATE server-side instead of silently running against nothing. multiTenant({ strict: true }). Every query ondbFor(tid)hasWHERE tenantId = $tidinjected, and inserts that try to set a differenttenantIdare rejected at compile time. No way to leak a row across tenants from a typo.useRuntimeConfig()for secrets.DATABASE_URLis read at runtime from env, not baked into the bundle.
nuxt.config.ts — runtime config exposing DATABASE_URL
app/
app.vue — task list + toggle + create
server/
utils/
schema.ts — shared table definitions
db.ts — Pool + sumak singleton, dbFor() factory
plugins/
close-pool.ts — ends the Pool on Nitro `close` (HMR + shutdown)
api/
tasks.get.ts — list
tasks.post.ts — create
tasks/[id].patch.ts — toggle done
export DATABASE_URL="postgres://postgres:pg@localhost:5432/postgres"
pnpm install
pnpm migrate
pnpm devSet a tid cookie in the browser (tid=1) to scope to tenant 1.
- Targets Node 24+. Nuxt 4 runs on older Node versions but the CLI example uses the
sumak migrate upcommand which expects a recent Node. compatibilityDatepins Nitro's feature surface — bump it when upgrading Nuxt / Nitro to pick up the matching defaults.