Developer Docs

Database Management

Managing PostgreSQL databases with Neon branching, running migrations, resetting data, and auditing database branches.

Database Management

DevStride uses Neon for PostgreSQL hosting, with Drizzle ORM for schema management and migrations. Every developer gets isolated database branches — like git branches, but for your database.

How Neon Branching Works

Neon provides instant, copy-on-write database branches:

main (staging data)
├── dev-phil-local              ← Phil's local dev branch (stable, branch-independent)
├── dev-phil-feature-auth       ← Phil's cloud deploy branch (created by ds deploy up)
├── dev-sarah-local             ← Sarah's local dev branch
└── dev-ci-pr-142               ← CI/CD ephemeral branch
  • main contains the latest staging data. All new branches fork from it.
  • Local branches (dev-{developer}-local) are used for local development. One per developer, stable across git branch switches.
  • Cloud branches (dev-{developer}-{branch}) are created by ds deploy up for isolated cloud testing. Each cloud stage gets its own separate database branch.
  • Branches are cheap — they use copy-on-write storage, so a branch with no changes uses almost no additional space.

Checking Your Current Database

Before running migrations or resets, it's handy to confirm exactly which database branches your environment is pointing to:

ds db current

This resolves your DB_CONNECTION_STRING and SOURCE_DB_CONNECTION_STRING to human-readable Neon project and branch names:

TARGET DB (DB_CONNECTION_STRING)
Project:  devstride-staging
Branch:   dev-phil-local
Host:     ep-xxx.us-east-1.aws.neon.tech

SOURCE DB (SOURCE_DB_CONNECTION_STRING)
Project:  devstride-prod-replica
Branch:   main
Host:     ep-yyy.us-east-1.aws.neon.tech

Migrations

Running Migrations

ds db migrate

Applies all pending Drizzle ORM migrations to your database. Migrations are idempotent — already-applied migrations are skipped. Shows an interactive Neon branch picker so you can confirm (or change) the target branch before running.

Options:

FlagDescription
--target <stage>Target a specific stage (default: your current DEVSTRIDE_STAGE)
--db <conn>Direct connection string — bypasses the interactive picker

Creating a Migration

When you modify a database schema:

  1. Edit the SQL entity file — e.g., backend/src/modules/notification/infrastructure/persistence/notification.sql-entity.ts
  2. Generate the migration:
    cd backend && pnpm generate-sql
    

    This compares your entity definitions against the current schema and generates a migration file in backend/drizzle/.
  3. Apply the migration:
    ds db migrate
    
  4. Restart the backend to pick up schema changes:
    ds local run backend
    

Branch Management

Neon branches are automatically provisioned by the tooling for standard workflows. You can also manage branches manually using the commands below.

  • ds setup creates your local branch (dev-{developer}-local) from main and writes the connection string to .env
  • ds deploy up (CI) creates a separate branch for each cloud stage (dev-{developer}-{branch}) and stores credentials in Secrets Manager
  • ds deploy down tears down the cloud branch along with all other cloud stage resources

Your local branch (dev-{developer}-local) is independent from cloud branches. Running ds db migrate locally applies migrations to your local branch. Cloud deployments run their own migrations against their own branches.

Create a Branch

ds db create-branch [name]

Creates a new Neon database branch from main and updates DB_CONNECTION_STRING in .env. If a branch with that name already exists, switches .env to point to it. Prompts for a name if not provided.

Restart the backend after switching branches.

Delete a Branch

ds db delete-branch [name]

Deletes a Neon database branch. The main branch is always protected. Shows an interactive picker if no name is provided. Warns if .env still points to the deleted branch.

List Neon Projects

ds db list-projects

Shows a formatted table of all Neon projects accessible with your API key — including project name, region, project ID, and creation date.

Switch Target Database

ds db set target-db

Opens an interactive Neon branch picker across all your projects and writes the selected branch's connection URI to DB_CONNECTION_STRING in .env. Use this to point your local backend at a different branch without manually editing .env.

Restart your backend after switching.

Switch Source Database

ds db set source-db

Same as set target-db, but writes to SOURCE_DB_CONNECTION_STRING. This is the database that ds db reset reads from when seeding data.

Resetting Data

Full Database Reset

ds db reset

Performs a destructive full reset of your database. This is a complete wipe-and-rebuild at the SQL level — not a Neon branch restore.

What happens:

  1. Terminates all other open connections to your database
  2. Drops the public schema entirely (DROP SCHEMA public CASCADE)
  3. Deletes all Cognito users in your stage's user pool
  4. Re-runs all Drizzle ORM migrations from scratch
  5. Seeds Stripe products
  6. Copies organization data from SOURCE_DB_CONNECTION_STRING into your database

Requirements:

  • SOURCE_DB_CONNECTION_STRING must be set in your .env (points to the data source to seed from)
  • Use ds db set source-db to configure this interactively if it's not already set

Options:

FlagDescription
--target <stage>Target a specific stage (default: your current DEVSTRIDE_STAGE)
-o, --organization <id>Source organization ID to copy
--source-db <conn>Override SOURCE_DB_CONNECTION_STRING
--target-db <conn>Override DB_CONNECTION_STRING

When to use:

  • You need a completely clean database matching a known-good source
  • Migrations got into an inconsistent state that can't be fixed incrementally
  • You want to reproduce a specific organization's data locally

Refresh Staging Data

ds db refresh-staging

This is an admin operation that refreshes the main Neon branch with production data:

  1. Dispatches a GitHub Actions workflow
  2. Copies a specified organization's data from production to the staging main branch
  3. All future developer branches will inherit this refreshed data
  4. Existing developer branches are not affected

Options:

FlagDescription
-o, --organization <id>Organization ID to copy (default: demo organization)

Use this when staging data has drifted too far from production, or when you need realistic data for testing.

Database Lifecycle Summary

Here's how database branches fit into the development lifecycle:

PhaseCommandWhat Happens
Setupds setupCreates your Neon branch from main automatically
Check current dbds db currentShows which Neon branches are active
Schema changepnpm generate-sql then ds db migrateGenerates and applies migration
Full resetds db resetSQL drop + migrate + seed from source org
Switch branchds db set target-dbInteractive picker to switch DB_CONNECTION_STRING
New branchds db create-branchFork a new Neon branch from main
Delete branchds db delete-branchRemove a stale Neon branch
Staging refreshds db refresh-stagingRefreshes main with latest prod data

Next Steps