Skip to content

Part 3.6 — Migrations, Environments & Local Dev Workflows

Site Console Site Console
4 min read Updated Jan 18, 2026 Databases 0 comments

One thing separates hobby projects from production-ready systems: disciplined environment and migration management.
The moment you work with teammates or deploy remotely, you need:

  • predictable environments

  • safe migrations

  • reproducible PostgreSQL setups

  • dev/preview/production database separation

  • automation for local workflows

This post guides you through building a clean, well-structured workflow you’ll use throughout the rest of this series.


1. Environment Files (.env) — Keep Secrets Out of Git

Every NestJS + Prisma backend needs separate environment files:

.env                # local
.env.development    # local explicit
.env.staging        # preview
.env.production     # production

Each file defines at least:

DATABASE_URL="postgresql://postgres:password@localhost:5432/fullstack_db"
PORT=3000
NODE_ENV=development

Best practices:

  • Never commit .env

  • Provide .env.example with placeholders

  • Use secrets in CI/CD (GitHub Actions → Encrypted Secrets)

  • Validate required env vars on startup (we’ll do this in Part 4 & 9)


2. Docker-Based Local PostgreSQL (No Local Installs Needed)

Your local DB should run in Docker so teammates can reproduce your setup instantly.

docker-compose.yml:

services:
  db:
    image: postgres:15
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: fullstack_db
    volumes:
      - dev_pg:/var/lib/postgresql/data

volumes:
  dev_pg:

Start it:

docker compose up -d

Now everyone has the same DB engine, same version, same config.


3. Running Prisma Against Your Local DB

Every time you change the schema:

pnpm prisma migrate dev --name descriptive_name

Example:

pnpm prisma migrate dev --name add_user_profile

This will:

  • generate SQL migrations

  • apply them to your local DB

  • update Prisma Client types


4. Resetting the Local Database (Useful for Dev)

pnpm prisma migrate reset

This will:

  • wipe all data

  • re-run all migrations

  • optionally re-run seed scripts

Perfect for testing evolving schemas.


5. Writing Seed Scripts (Populate Fake Data)

File: prisma/seed.ts

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

async function main() {
  await prisma.user.create({
    data: {
      name: 'Alice',
      email: 'alice@example.com',
      posts: {
        create: [
          { title: 'Hello World', body: 'My first post' },
          { title: 'React + NestJS', body: 'Full stack is fun!' },
        ],
      },
    },
  });
}

main()
  .catch(e => {
    console.error(e);
    process.exit(1);
  })
  .finally(() => prisma.$disconnect());

Enable seeding in package.json:

"prisma": {
  "seed": "ts-node prisma/seed.ts"
}

Run:

pnpm prisma db seed

React frontend development becomes easier with sample data.


6. Managing Multiple Databases (Dev, Staging, Prod)

You must separate environments:

Local / Dev

DATABASE_URL="postgresql://postgres:password@localhost:5432/fullstack_dev"

Staging

DATABASE_URL="${{ secrets.STAGING_DATABASE_URL }}"

Production

DATABASE_URL="${{ secrets.PROD_DATABASE_URL }}"

Your backend must NOT talk to production locally — one wrong migration can destroy data.


7. How CI/CD Runs Migrations (Important)

In GitHub Actions (Part 11), you’ll need something like:

- name: Apply migrations
  run: pnpm prisma migrate deploy

Never use migrate dev in CI or production.
Use:

pnpm prisma migrate deploy

This applies generated migrations without generating new ones.

This matters for:

  • schema stability

  • predictable production state

  • safe rollouts


8. Using Preview Databases for Feature Branches

Platforms like Render, Fly.io, and Neon support ephemeral DBs.

Workflow:

  • Create preview environment per PR

  • Migrate preview DB

  • Deploy preview API

  • Frontend points to it via VITE_API_URL

Your team can test features with real data before merging.


9. Auto-Generating Prisma Clients in CI

Whenever your schema changes:

GitHub Actions should run:

- name: Generate Prisma Client
  run: pnpm prisma generate

This ensures:

  • TypeScript builds correctly

  • NestJS services have updated types

  • No breaking migrations sneak into PRs


10. Making .env Available in NestJS

NestJS uses @nestjs/config:

pnpm add @nestjs/config

In app.module.ts:

ConfigModule.forRoot({
  isGlobal: true,
  envFilePath: ['.env', `.env.${process.env.NODE_ENV}`],
});

Usage:

constructor(private config: ConfigService) {}

const dbUrl = this.config.get<string>('DATABASE_URL');

This abstracts environment management neatly.


11. Creating a Reproducible Local Dev Script

Improve DX with pnpm scripts:

"scripts": {
  "dev": "nest start --watch",
  "db:start": "docker compose up -d",
  "db:stop": "docker compose down",
  "migrate": "prisma migrate dev",
  "seed": "prisma db seed"
}

Add these for smoother workflows.


12. Versioning Migrations and Schema Changes

Rules:

  • never edit old migrations

  • if schema changes → create new migration

  • treat migrations as history

  • review every SQL file

  • require PR approval for schema updates (important in teams)

A clean migration history equals painless deployments.


13. Handling Breaking Database Changes Safely

When changing columns or relations:

  • plan additive → then destructive

  • migrate with null defaults → then backfill → then remove defaults

  • never drop columns without a migration safety window

  • use feature flags

PostgreSQL is unforgiving; structure changes must be deliberate.


14. Summary

You now understand:

  • how to configure portable .env setups

  • how to use Dockerized PostgreSQL

  • how Prisma migrations work in dev

  • how migrations are applied in CI/CD

  • how to seed local databases

  • how to manage dev/staging/prod environments safely

  • how to build workflows that scale with your team

This completes Part 3 — Backend Foundations with NestJS + Prisma.

Next up:
➡️ Create Series for Part 4 — Testing the Backend: Jest, Supertest, and Auth Guards

Related

Leave a comment

Sign in to leave a comment.

Comments