Every verified stack that runs on Serverless (Vercel / Lambda).
Serverless functions: many short-lived instances, one DB connection each, behind Neon's pooler.
0 verified stacks
No verified stacks published yet.
What Serverless (Vercel / Lambda) gives you
This client is sized for a runtime where the platform, not the process, decides concurrency. Vercel functions and Lambda run many short-lived instances, each serving one request or a handful before freezing, and whatever pool an instance opens gets multiplied by however many instances the platform chose to spin up. So `dbClient` emits `postgres(process.env.DATABASE_URL!, { prepare: false, max: 1 })` — one backend per instance — and leaves the multiplexing to Neon's PgBouncer endpoint, which is the only layer that can see across processes. The MySQL branch makes the same bet with `mysql2`'s knob: `mysql.createPool({ uri: process.env.DATABASE_URL!, connectionLimit: 2 })`. Each is then bound to Drizzle (`drizzle(sql)` over `drizzle-orm/postgres-js`, `drizzle({ client: pool })` over `drizzle-orm/mysql2`) and exported as `db`, alongside the raw `sql` / `pool`.
The failure this avoids is connection exhaustion, and its nastiness is that it is a load failure rather than a compile failure. A `max: 10` pool behaves perfectly in local dev and in a single smoke-test request; the first time traffic fans out it becomes ten times the instance count, Postgres starts refusing connections at `max_connections`, and the code that caused it still reads fine in review. Driver-side pooling is simply the wrong tool at this shape — one connection per instance plus a real pooler is. `prepare: false` is the second half of the same decision. `DATABASE_URL` here is Neon's pooled `-pooler` host, meaning PgBouncer in transaction mode, which hands a different backend to each transaction; a statement prepared on one is missing on the next.
The same property rules out everything else that assumes a stable session — `LISTEN`/`NOTIFY`, session-scoped `SET`, advisory-lock sessions, server-side cursors, `WITH HOLD`. Those paths need Neon's direct endpoint, and only those paths.
Built for
`max: 1` in the emitted `postgres()` call caps each function instance at a single backend, so total connections track the pooler's ceiling rather than instance count multiplied by pool size.
`prepare: false` turns off prepared statements, which do not survive an endpoint that may hand each transaction a different backend — the default that silently breaks the moment the app is pointed at the `-pooler` host.
The `mysql2` branch creates a pool of two (`connectionLimit: 2`) — not the ten a persistent process would take — so the same fan-out arithmetic that governs the Postgres branch stays bounded on MySQL.
`export const sql` is the configured `postgres` tagged template itself (`pool` in the MySQL branch), so one-off queries and migration scripts run through exactly the connection settings Drizzle's `db` uses, not a second ad-hoc client.
`DATABASE_URL` is documented as the pooled `-pooler` string, which is what makes `max: 1` and `prepare: false` correct together; the direct endpoint stays reserved for the session-scoped work transaction mode forbids.
Why Serverless (Vercel / Lambda), specifically
max: 1 per instance + Neon's pooler is the rule — a large max times serverless fan-out exhausts Postgres connections.
prepare: false — Neon's pooled endpoint is PgBouncer transaction mode (prepared statements break).
Transaction mode forbids anything that spans transactions on one backend: LISTEN/NOTIFY, session-scoped SET, advisory-lock sessions, server-side cursors, WITH HOLD. Use Neon's direct (non-pooled) endpoint for those paths only.
Keep idle_timeout ~20s / connect_timeout ~10s so frozen instances release backends fast; lean on PgBouncer, never on driver pooling, to stay under the project's max_connections.
What Serverless (Vercel / Lambda) pulls in
bun add postgres drizzle-ormDATABASE_URLNeon POOLED (-pooler) connection string