Database Setup
How to configure PostgreSQL with Prisma 7 for Next Starter, including local Docker setup, running migrations, hosted database providers, and connection pooling for serverless deployments.
Overview
Next Starter uses PostgreSQL with Prisma 7 as the ORM. The Prisma client is generated into generated/prisma (not node_modules) and is gitignored. It regenerates as part of pnpm build, which runs prisma generate && prisma migrate deploy && next build.
Local PostgreSQL with Docker
The project includes a docker-compose.yml that starts a PostgreSQL 17 instance with the default credentials from .env.example.
docker-compose up -dThis creates a next_starter database accessible at localhost:5432.
Default credentials:
| Setting | Value |
|---|---|
| Host | localhost |
| Port | 5432 |
| User | postgres |
| Password | postgres |
| Database | next_starter |
The corresponding connection string:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/next_starter"Data persists in a named Docker volume (postgres_data). It survives container restarts but is removed when you run docker-compose down -v.
Prisma Configuration
Prisma 7 separates schema and datasource configuration. The database URL is not defined inside schema.prisma. It is configured in prisma.config.ts at the project root:
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: env("DATABASE_URL"),
},
});DATABASE_URL must be set in your environment (or .env file) before running any Prisma commands.
Prisma Schema
The schema lives at prisma/schema.prisma. It defines five models used by Better Auth and the billing system:
generator client {
provider = "prisma-client"
output = "../generated/prisma"
}
datasource db {
provider = "postgresql"
}
model User {
id String @id
name String
email String @unique
emailVerified Boolean
image String?
createdAt DateTime
updatedAt DateTime
role String?
banned Boolean?
banReason String?
banExpires DateTime?
accounts Account[]
sessions Session[]
settings Json?
stripeCustomerId String?
@@map("user")
}
model Session {
id String @id
expiresAt DateTime
token String @unique
createdAt DateTime
updatedAt DateTime
ipAddress String?
userAgent String?
userId String
impersonatedBy String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@map("session")
}
model Account {
id String @id
accountId String
providerId String
userId String
accessToken String?
refreshToken String?
idToken String?
accessTokenExpiresAt DateTime?
refreshTokenExpiresAt DateTime?
scope String?
password String?
createdAt DateTime
updatedAt DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@map("account")
}
model Verification {
id String @id
identifier String
value String
expiresAt DateTime
createdAt DateTime?
updatedAt DateTime?
@@index([identifier])
@@map("verification")
}
model Subscription {
id String @id
plan String
referenceId String
stripeCustomerId String?
stripeSubscriptionId String?
status String? @default("incomplete")
periodStart DateTime?
periodEnd DateTime?
trialStart DateTime?
trialEnd DateTime?
cancelAtPeriodEnd Boolean? @default(false)
cancelAt DateTime?
canceledAt DateTime?
endedAt DateTime?
seats Int?
@@map("subscription")
}Do not modify this schema to add Better Auth fields manually. Use the Better Auth CLI to generate schema additions when enabling new plugins, then run migrations.
Running Migrations
Development
pnpm dlx prisma migrate devThis applies pending migrations, creates a new migration file if the schema has changed, and regenerates the Prisma client.
Production
pnpm dlx prisma migrate deploymigrate deploy applies pending migrations without creating new ones and without prompting. pnpm build already runs prisma migrate deploy before building the app, so most deployment pipelines do not need to run this separately. It is safe to run on every deploy: it is a no-op when the database is already up to date.
Checking Migration Status
pnpm dlx prisma migrate statusShows which migrations have been applied and which are pending.
Seeding Data
No seed file is included by default. To add one, create prisma/seed.ts and add a prisma.seed entry to package.json:
{
"prisma": {
"seed": "tsx prisma/seed.ts"
}
}Then run:
pnpm dlx prisma db seedA typical seed file for development creates an admin user or inserts test subscription data.
Resetting the Database
To wipe all data and re-apply migrations from scratch (development only):
docker-compose down -v && docker-compose up -d && pnpm dlx prisma migrate devThis removes the Docker volume (all data), starts a fresh PostgreSQL instance, and re-applies all migrations.
Hosted Database Providers
For production, use a managed PostgreSQL provider. All of the following work with Prisma by setting DATABASE_URL to the connection string they provide.
Neon
Neon offers serverless PostgreSQL with a generous free tier and built-in connection pooling, which works well in serverless environments.
- Create a project in the Neon dashboard.
- Copy the connection string from Connection Details.
- Set it as
DATABASE_URLin your environment.
Neon connection strings include ?sslmode=require. Prisma handles this automatically.
Supabase
Supabase provides managed PostgreSQL with a dashboard, row-level security, and built-in storage. Next Starter only uses the PostgreSQL database, not Supabase Auth or Supabase Storage.
- Create a project in the Supabase dashboard.
- Go to Settings > Database and copy the connection string under Connection string > URI.
- Replace
[YOUR-PASSWORD]with your database password. - Set it as
DATABASE_URL.
Railway
Railway provisions a PostgreSQL database alongside your app deployment in the same project. The connection string is available as an environment variable in the Railway dashboard.
- Add a PostgreSQL plugin to your Railway project.
- Copy
DATABASE_URLfrom the plugin's Variables tab. - Add it to your app service's environment variables.
Railway is a good choice for hosting the app container and database in one place.
Connection Pooling
For serverless deployments (Vercel, AWS Lambda), each function invocation may open a new database connection. PostgreSQL has a limited connection count, so high traffic can exhaust the pool.
Use a connection pooler in front of your database:
- Neon: built-in pooling via the
?pgbouncer=trueparameter in the connection string - Supabase: use the pooler connection string (port 6543) instead of the direct connection
- PgBouncer: self-hosted pooler, available as a Railway plugin or run separately
When using a pooler, keep DATABASE_URL pointed at the pooled connection string for the application. For running migrations, you need a direct (non-pooled) connection because connection poolers in transaction mode are incompatible with Prisma migrations.
The .env.example documents a DIRECT_DATABASE_URL variable as a placeholder for this purpose. To use it, update prisma.config.ts to read from DIRECT_DATABASE_URL when running migrations, or temporarily set DATABASE_URL to the direct connection string before running prisma migrate deploy.
Example environment variables:
DATABASE_URL="postgresql://user:password@pooler-host:6543/dbname?pgbouncer=true&connection_limit=1"
DIRECT_DATABASE_URL="postgresql://user:password@direct-host:5432/dbname"DIRECT_DATABASE_URL is not automatically wired into prisma.config.ts. It is provided in .env.example as a reference. To make Prisma use the direct URL for all migration commands, update prisma.config.ts:
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: env("DIRECT_DATABASE_URL"),
},
});For long-running server deployments (Docker, Railway app service), connection pooling is less critical because the process stays alive and Prisma reuses connections.
reCAPTCHA v3
Next Starter integrates Google reCAPTCHA v3 to protect sign-in, registration, and contact forms from bots, with server-side score verification and no user-facing challenges.
Deploy to Vercel
Step-by-step guide to deploying Next Starter on Vercel, including all required environment variables, Stripe webhook configuration, custom domain setup, and preview deployment best practices.