Skip to content

Neon

Neon is Sadie’s canonical production database. The serverless HTTP driver (@neondatabase/serverless) is stateless, has no pool to warm up, and runs happily on the Cloudflare worker that hosts the app.

Sadie runs on Cloudflare Pages via OpenNext. That environment does not keep a persistent TCP pool between requests. A classic pg pool would open and close a socket per cold start, which is both slow and wasteful. The Neon HTTP driver sidesteps the problem: every query is a single stateless HTTP round-trip. It also means DATABASE_URL can be used from any region without a bouncer.

Branches on Neon are a production-grade staging story. You branch off main for preview deploys and flip back in seconds.

If you have the Neon MCP installed in Claude Code, ask:

Create a Neon project called sadie in my org, give me the pooled connection string.

The MCP returns a URL of the form:

postgresql://<user>:<password>@ep-xxxxxxxx-pooler.<region>.aws.neon.tech/<db>?sslmode=require

Paste it into .env.local as DATABASE_URL. The .neon.tech hostname auto-selects the HTTP driver.

  1. Sign in at neon.tech.
  2. Create a new project. Region close to your users. Postgres 16.
  3. Copy the pooled connection string from the Dashboard. Use the pooled one. The direct endpoint works too, but the pooler is the sensible default for serverless.
  4. Paste into .env.local.
Terminal window
DATABASE_URL=postgresql://USER:PASS@ep-xxxxxxxx-pooler.REGION.aws.neon.tech/DB?sslmode=require
Terminal window
pnpm db:migrate

That replays the hand-crafted migrations in packages/db/drizzle/ in order. The first one (0000_init.sql) creates every table. Subsequent ones (0001_*, 0002_mind_to_soul.sql) are preserving renames and backfills.

If this is a brand-new database and you want to skip the migration history and just diff the schema in one shot:

Terminal window
pnpm db:push

db:push is destructive for renames. Use it on empty databases only. See Migrations for the tradeoff.

Create a branch off main for every preview:

Terminal window
# via the Neon MCP:
# "create a branch called preview-foo off main, give me the connection string"
# via the dashboard:
# Branches → New branch → copy connection string

Wire that branch’s URL into a Cloudflare Pages preview environment variable. The branch starts as a zero-cost copy of production state and accepts writes independently.

The app auto-detects the driver by hostname. If you ever proxy Neon through a non-.neon.tech hostname, force the HTTP driver explicitly:

Terminal window
SADIE_DB_DRIVER=neon-http

The reverse (forcing postgres-js against a Neon URL) is rarely useful but supported via SADIE_DB_DRIVER=postgres.

See packages/db/src/client.ts for the exact driver selection logic. The short version:

  • Hostname ends in .neon.tech or .neon.buildneon-http (stateless).
  • Anything else → postgres-js (pooled TCP, SADIE_DB_POOL_MAX defaults to 5).