Database
Local Postgres via Docker, migrations, seeding, and the smoke check.
Local setup
Local dev runs Postgres 17 in Docker, matching the schema and driver used in production.
The connection string is pre-filled in .env.local_template:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/appdocker compose up -d # start Postgres on :5432
docker compose down # stop (keeps the data volume)
docker compose down -v # stop and wipe all dataIf Postgres is already running on :5432 (another project, a standalone
container), docker compose up -d will fail silently. Check what's running
with docker ps and stop the conflicting container first. This repo's
container is named next-template-postgres.
Migrations
Migrations are generated from src/db/schema.ts using Drizzle Kit and live in drizzle/.
pnpm db:generate # generate a new SQL migration from schema changes
pnpm db:migrate # apply all pending migrations (idempotent, safe to re-run)Always run pnpm db:migrate after a fresh clone or after pulling changes that include
new migrations.
Seeding
pnpm db:seedClears example_items and inserts 3 fixed demo rows. Useful for resetting to a known
state when testing the example CRUD feature. Skip if you have data you want to keep.
Smoke check
pnpm db:checkA fast, read-only probe of the full DATABASE_URL → pg.Pool → Drizzle → example_items
path. Useful after a fresh clone or when the DB is misbehaving - it confirms the
connection, driver, and schema are all wired correctly without mutating any data.
Studio
pnpm db:studioOpens Drizzle Studio - a browser-based table viewer and editor. Useful for inspecting data during development.
Production
For production, replace DATABASE_URL with your hosted provider's connection string.
Neon is the recommended option - see Neon Setup
for the setup steps. No code changes are needed; only the env var changes.
Extending with caching
This template uses Postgres for all persistence. As your app grows you may reach a point where certain workloads benefit from a cache layer - common triggers are:
- Rate limiting API routes (the most common early need)
- Caching expensive queries to avoid redundant DB round-trips across serverless invocations
- Background job queues via QStash
Upstash Redis is the natural fit for this stack. It is serverless-billed
(pay per request, not per hour), connects over HTTP so it works correctly in Vercel's
serverless environment, and has a native Vercel integration that injects env vars automatically.
The setup mirrors Neon: create a database, add two env vars
(UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN), and you're connected.
When you add it, follow the same service pattern as everything else in classes/ - a
classes/services/cache/ singleton wrapping @upstash/redis. Rate limiting drops into
proxy.ts via @upstash/ratelimit without touching any other layer.
Don't add it until you have a concrete reason to. Postgres handles far more load than most early-stage products ever generate.