Back to Blog
Jun 9, 20264 min readOnuzulike Anthony

Neon's Database Branching for Fearless Development

Creating database branches for PRs, running migrations safely, restoring from branch history, and integrating with CI.

Full-StackPostgreSQLNeonDatabase

Neon is a serverless PostgreSQL provider with a killer feature: database branching. Just like Git branches for code, you can branch your database — spin up an isolated copy of your schema and data, run migrations against it, test, then merge or discard. NodWatch uses this for every non-trivial schema change.

Why Branching Matters

The classic problem: you write a migration, run it against your dev database, push to production, and discover the migration is wrong when it's too late. Rolling back a destructive migration (DROP COLUMN, ALTER TYPE) can mean data loss.

With branching:

  1. Create a branch from main (production snapshot)
  2. Run your migration against the branch
  3. Test with real production-like data
  4. If it works, run the same migration against production
  5. If it breaks, discard the branch — main is untouched

Setup

Install the Neon CLI:

bash
npm install -g neonctl
neonctl auth  # follow the browser flow

Creating Branches

bash
# Create a branch from main
neonctl branches create --name feature/add-download-jobs --project-id YOUR_PROJECT_ID
 
# Get the connection string for the branch
neonctl connection-string feature/add-download-jobs
 
# Output: postgresql://user:pass@ep-xyz.us-east-2.aws.neon.tech/neondb

Set the branch connection string as your dev DATABASE_URL and run migrations:

bash
DATABASE_URL="postgresql://..." npx drizzle-kit migrate

Resetting a Branch

If something goes wrong, reset to the parent branch state:

bash
neonctl branches reset feature/add-download-jobs --parent

This is instant — Neon stores branch metadata, not data copies. Branches share storage with copy-on-write semantics.

Point-in-Time Restore

Neon keeps a 7-day (paid: 30-day) history. Restore to any point:

bash
# Restore a branch to 2 hours ago
neonctl branches restore feature/add-download-jobs --timestamp "2026-06-09T10:00:00Z"

This is a lifesaver for testing "what was the state before the bad migration?"

CI Integration

In GitHub Actions, create a branch per PR, run migrations and tests against it, then delete on merge:

yaml
# .github/workflows/pr-db.yml
name: PR Database Setup
on: [pull_request]
 
jobs:
  setup-db:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Install Neon CLI
        run: npm install -g neonctl
 
      - name: Create DB branch
        id: db-branch
        run: |
          BRANCH_NAME="pr-${{ github.event.number }}"
          neonctl branches create --name $BRANCH_NAME --project-id ${{ secrets.NEON_PROJECT_ID }}
          CONNECTION_STRING=$(neonctl connection-string $BRANCH_NAME)
          echo "connection_string=$CONNECTION_STRING" >> $GITHUB_OUTPUT
        env:
          NEON_API_KEY: ${{ secrets.NEON_API_KEY }}
 
      - name: Run migrations
        run: npx drizzle-kit migrate
        env:
          DATABASE_URL: ${{ steps.db-branch.outputs.connection_string }}
 
      - name: Run tests
        run: npm test
        env:
          DATABASE_URL: ${{ steps.db-branch.outputs.connection_string }}
 
  cleanup-db:
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request' && github.event.action == 'closed'
    steps:
      - name: Delete DB branch
        run: neonctl branches delete "pr-${{ github.event.number }}" --project-id ${{ secrets.NEON_PROJECT_ID }}
        env:
          NEON_API_KEY: ${{ secrets.NEON_API_KEY }}

Each PR gets an isolated database with the full production schema and recent data. Tests run against a real Postgres instance.

Neon + Drizzle Migration Workflow

My full migration workflow:

bash
# 1. Create a feature branch in Git and Neon simultaneously
git checkout -b feature/user-preferences
neonctl branches create --name feature/user-preferences
 
# 2. Set .env.local to use the branch connection string
# DATABASE_URL=postgresql://...feature-branch...
 
# 3. Modify schema
# db/schema.ts: add userPreferences table
 
# 4. Generate migration
npx drizzle-kit generate
 
# 5. Apply to branch
npx drizzle-kit migrate
 
# 6. Test locally
 
# 7. Merge code to main, apply migration to production
DATABASE_URL=$PRODUCTION_URL npx drizzle-kit migrate
 
# 8. Delete the branch
neonctl branches delete feature/user-preferences

Autoscaling

Neon scales to zero when idle — great for dev branches that sit unused between coding sessions. NodWatch's dev branch scales from 0 to handling queries in about 500ms cold start. Production stays warm with autoscaling configured between 0.25 and 4 CUs.

The combination of branching + the serverless HTTP driver via Hyperdrive means you get a production-grade Postgres setup with developer ergonomics that actually work.