Next Starter Logo
Deployment

Database Setup

How Next Starter connects PostgreSQL to Prisma 7: local Docker, migrations, managed providers, and connection pooling for serverless deployments.

How it works

Next Starter stores data in PostgreSQL and reaches it through Prisma 7, which ships no query engine binary. Queries run through a driver adapter instead, and this app uses @prisma/adapter-pg on top of node-postgres.

PieceFileWhat it does
Schema + generatorprisma/schema.prismaYour models and the prisma-client generator
Config + migrationsprisma.config.tsPoints Prisma at the schema and reads DATABASE_URL
Client instancelib/db.tsSets up the adapter and exports one shared client
Local databasedocker-compose.ymlPostgres 18 for development

Prisma generates the typed client into generated/prisma rather than node_modules, and that folder is gitignored. Because the output is a custom folder, the generator needs an explicit output path:

generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "postgresql"
}

Note the missing url on that datasource. Prisma 7 doesn't read the database URL from schema.prisma at all. CLI commands take it from prisma.config.ts (env("DATABASE_URL")), and the running app takes it from the adapter in lib/db.ts. Either way, DATABASE_URL has to be set before any prisma command runs. That config file opens with import "dotenv/config", which is what lets the CLI see your local .env.

lib/db.ts builds a PrismaPg adapter from DATABASE_URL, hands it to PrismaClient, and caches the instance on globalThis so a hot reload in development doesn't open a fresh connection every time you save a file. Import the default export:

import prisma from "@/lib/db";

Get Started covers getting Postgres running on your machine.

The models

prisma/schema.prisma defines six models. Four back Better Auth core, Subscription backs billing, and Apikey backs the API key plugin.

ModelPurpose
UserAccounts, plus role/banned (admin), onboardingComplete, settings, stripeCustomerId
SessionActive sessions, including admin impersonation
AccountOAuth and password links for each user (providerId, accountId, tokens, hashed password)
VerificationEmail and token verification
SubscriptionStripe subscription state (plan, status, period, seats)
ApikeyPer-user API keys (hashed key, referenceId = user ID, rate limit and expiry fields)

Don't hand-edit the auth models to add Better Auth fields. Enable the plugin, run the Better Auth CLI to write the schema changes, then migrate. See Authentication.

What you can change

Add your own model

Add the model to prisma/schema.prisma, then run both commands:

pnpm prisma migrate dev --name add_post
pnpm prisma generate

The first writes a migration file into prisma/migrations/ and applies it to your database. The second is not optional. migrate dev doesn't regenerate the client, so skip it and prisma.post won't exist on the typed client yet.

Build your first SaaS app walks a model through to a working feature.

Point at a managed Postgres

Set DATABASE_URL to any managed Postgres connection string (Neon, Supabase, Railway, RDS, and so on). Nothing else changes, because the adapter speaks standard Postgres. Add sslmode=require if the provider wants TLS, which most do, then apply your schema with prisma migrate deploy.

Commands

CommandWhen to use it
pnpm prisma migrate devDev: create and apply a migration. Follow it with generate
pnpm prisma migrate deployProd: apply pending migrations, no prompts (also the last step of pnpm build)
pnpm prisma migrate statusShow which migrations are applied and which are still pending
pnpm prisma generateRegenerate generated/prisma. migrate dev doesn't run it; pnpm build does
pnpm prisma studioBrowse and edit data in a local GUI

migrate deploy is safe to run on every deploy. It does nothing when the database is already up to date.

Seeding

No seed file ships by default. Create prisma/seed.ts, register the command in prisma.config.ts, then run pnpm prisma db seed. The CLI reads the seed command from the migrations block of that config file:

migrations: {
  path: "prisma/migrations",
  seed: "pnpm dlx tsx prisma/seed.ts",
},

Connection pooling for serverless

On Vercel or Lambda, every cold start can open its own database connection, and enough of them at once will exhaust the Postgres connection limit. Put a pooler in front and point DATABASE_URL at the pooled connection string. Neon and Supabase both provide one; PgBouncer is the self-hosted route.

Migrations want a direct, non-pooled connection, because transaction-mode pooling drops the session state some migration steps rely on. The template has no variable for that string. prisma.config.ts reads DATABASE_URL and nothing else, so the build-time migrate deploy goes through whatever URL you gave the app. To split the two, add a variable such as DIRECT_DATABASE_URL to your environment and point the prisma.config.ts datasource at it. That swaps the URL for every Prisma CLI command, migrations included, and leaves the running app on DATABASE_URL since lib/db.ts reads that separately:

datasource: {
  url: env("DIRECT_DATABASE_URL"),
},

A ?connection_limit= parameter on the URL does nothing here. The pool belongs to pg, so you size it by passing a max alongside connectionString in lib/db.ts.

For long-running deployments (Docker, a Railway service) pooling matters less. The process stays alive, so Prisma reuses its connections.

On this page