DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Prisma in Production: Migrations, Safe Schema Changes, and Connection Pooling

Prisma Migrations in Production

Running prisma migrate dev in production is wrong. Here's the correct workflow.

Development vs Production Migration Commands

# Development: creates migration files + applies them
npx prisma migrate dev --name add_user_table

# Production: applies existing migration files ONLY
npx prisma migrate deploy

# Never in production:
# npx prisma migrate dev       <- drops/resets schemas
# npx prisma db push           <- schema drift, no migration history
# npx prisma migrate reset     <- drops all data
Enter fullscreen mode Exit fullscreen mode

Deployment Pipeline

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Run database migrations
        run: npx prisma migrate deploy
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}

      - name: Build application
        run: npm run build

      - name: Deploy to Vercel
        run: npx vercel --prod
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

Safe Schema Changes

Some migrations are safe. Some cause downtime. Know the difference.

Safe (zero downtime):

-- Adding a nullable column
ALTER TABLE users ADD COLUMN bio TEXT;

-- Adding an index
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);

-- Adding a table
CREATE TABLE notifications (...);
Enter fullscreen mode Exit fullscreen mode

Dangerous (requires care):

-- Renaming a column (breaks old code still reading old name)
ALTER TABLE users RENAME COLUMN username TO handle;

-- Adding NOT NULL without a default (fails on existing rows)
ALTER TABLE users ADD COLUMN plan VARCHAR NOT NULL; -- FAILS

-- Dropping a column (old deploys still reference it)
ALTER TABLE users DROP COLUMN old_field;
Enter fullscreen mode Exit fullscreen mode

The Expand-Contract Pattern

For zero-downtime column rename:

Step 1: Add new column (nullable)
  ALTER TABLE users ADD COLUMN handle TEXT;

Step 2: Deploy code that writes to BOTH old and new column
  user.username = value
  user.handle = value

Step 3: Backfill: UPDATE users SET handle = username;

Step 4: Deploy code that reads from new column only

Step 5: Drop old column
  ALTER TABLE users DROP COLUMN username;
Enter fullscreen mode Exit fullscreen mode

Prisma Schema Patterns

model User {
  id          String    @id @default(cuid())
  email       String    @unique
  name        String?
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt

  // Relations
  subscription  Subscription?
  posts         Post[]
  apiKeys       ApiKey[]

  @@index([email])  // Index for email lookups
}

model Subscription {
  id                String    @id @default(cuid())
  userId            String    @unique
  user              User      @relation(fields: [userId], references: [id], onDelete: Cascade)
  stripeCustomerId  String?   @unique
  stripePriceId     String?
  plan              String    @default("free")
  status            String    @default("active")
  currentPeriodEnd  DateTime?
  createdAt         DateTime  @default(now())
  updatedAt         DateTime  @updatedAt

  @@index([stripeCustomerId])
}
Enter fullscreen mode Exit fullscreen mode

Connection Pooling

Serverless environments (Vercel) open a new DB connection per request. Use PgBouncer or Prisma Accelerate:

// lib/db.ts
import { PrismaClient } from '@prisma/client'

// Prevent multiple instances in dev (hot reload)
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }

export const db = globalForPrisma.prisma ?? new PrismaClient({
  log: process.env.NODE_ENV === 'development' ? ['query'] : [],
})

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db
Enter fullscreen mode Exit fullscreen mode

For Vercel, use Prisma Accelerate or Neon's connection pooling:

# .env
DATABASE_URL="prisma://accelerate.prisma-data.net/?api_key=..."
DIRECT_URL="postgresql://user:pass@host/db"  # For migrations
Enter fullscreen mode Exit fullscreen mode
datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")    // Pooled (Accelerate)
  directUrl = env("DIRECT_URL")      // Direct (migrations)
}
Enter fullscreen mode Exit fullscreen mode

Pre-Configured in the AI SaaS Starter

The AI SaaS Starter Kit ships with Prisma configured for Vercel deployment, connection pooling set up, User + Subscription + ApiKey schemas ready, and migration CI/CD included.

$99 one-time at whoffagents.com

Top comments (0)