Updated for nself v0.4.8
Understanding how nself handles database migrations with automatic generation, version control, and safe team workflows.
nself v0.4.8 provides comprehensive database migration management through the nself db migrate command suite. Migrations are stored in nself/migrations/ and support both auto-generated and custom migration files.
Edit your schema.dbml file or design visually at dbdiagram.io.
# Create migration from DBML
nself db schema import schema.dbml
# Or create a custom migration manually
nself db migrate create add_posts_table
# Example output:
# ✓ Schema changes detected!
# ✓ Migration created: nself/migrations/001_add_posts_table.sqlAlways review the generated migration files in nself/migrations/:
-- Migration: 001_add_posts_table
-- Created: 2026-01-22
-- UP
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
title VARCHAR(255) NOT NULL,
content TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_posts_user_id ON posts(user_id);
-- DOWN
DROP TABLE IF EXISTS posts;# Check migration status first
nself db migrate status
# Apply all pending migrations
nself db migrate up
# Apply specific number of migrations
nself db migrate up 3nself db migrate statusCheck current migration status:
# Show migration status
nself db migrate status
# Example output:
# Migration Status
# ────────────────
# 001_create_users Applied 2026-01-20
# 002_add_preferences Applied 2026-01-21
# 003_create_orders Pendingnself db migrate upApply pending migrations:
# Apply all pending migrations
nself db migrate up
# Apply specific number of migrations
nself db migrate up 3nself db migrate downRollback migrations:
# Rollback last migration
nself db migrate down
# Rollback specific number
nself db migrate down 2nself db migrate createCreate custom migrations:
# Create new migration
nself db migrate create add_user_indexes
# Creates: nself/migrations/004_add_user_indexes.sqlnself db migrate freshDrop all and re-run (development only):
# WARNING: NON-PRODUCTION ONLY
nself db migrate fresh
# This drops all tables and re-runs all migrationsnself db migrate repairFix migration tracking table if corrupted:
# Repair migration tracking
nself db migrate repairMigrations are stored in the nself/migrations/ directory:
nself/
├── migrations/
│ ├── 001_create_users.sql
│ ├── 002_add_preferences.sql
│ └── 003_create_orders.sql
├── seeds/
│ ├── common/
│ ├── local/
│ ├── staging/
│ └── production/
└── config/
└── prod-users.json-- Migration: 001_create_users
-- Created: 2026-01-22
-- UP
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- DOWN
DROP TABLE IF EXISTS users;Generated from DBML changes:
-- Adding a new table
CREATE TABLE categories (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
slug VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
created_at TIMESTAMP DEFAULT now() NOT NULL
);
-- Adding indexes
CREATE INDEX idx_categories_slug ON categories(slug);
CREATE INDEX idx_categories_created_at ON categories(created_at);Custom migrations for data changes:
-- Populate default categories
INSERT INTO categories (name, slug, description) VALUES
('Technology', 'technology', 'Technology related posts'),
('Business', 'business', 'Business and entrepreneurship'),
('Lifestyle', 'lifestyle', 'Lifestyle and personal development');
-- Update existing posts with default category
UPDATE posts
SET category_id = (SELECT id FROM categories WHERE slug = 'technology')
WHERE category_id IS NULL;-- Create updated_at trigger function
CREATE OR REPLACE FUNCTION trigger_set_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Apply trigger to tables
CREATE TRIGGER set_timestamp
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION trigger_set_timestamp();
CREATE TRIGGER set_timestamp
BEFORE UPDATE ON posts
FOR EACH ROW
EXECUTE FUNCTION trigger_set_timestamp();nself db run to generate migrationnself db migrate:upnself db update to safely apply migrations# For all team members (recommended)
nself db update
# This command:
# 1. Creates automatic backup
# 2. Applies pending migrations
# 3. Verifies database integrity
# 4. Provides rollback option if issues occurAll migration commands respect the current environment:
| Environment | Behavior |
|---|---|
local | Full access, no confirmations |
staging | Warning prompts for destructive ops |
production | Blocked/confirmed destructive ops |
nself db migrate fresh - Blockednself db mock - Blockednself db reset - BlockedThese require typing yes-destroy-production:
nself db restorenself db migrate down# Set environment
ENV=production nself db migrate up
# Production migration includes:
# - Pre-migration backup
# - Schema validation
# - Confirmation prompt
# - Rollback preparation# View current migration status
nself db migrate status
# Example output:
# Migration Status
# ────────────────
# VERSION NAME STATUS APPLIED
# 001 create_users Applied 2026-01-20 10:30
# 002 add_preferences Applied 2026-01-21 14:15
# 003 create_orders Pending --- Check if column exists before adding
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'users' AND column_name = 'phone'
) THEN
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
END IF;
END $$;-- Migrate data in batches to avoid locks
DO $$
DECLARE
batch_size INTEGER := 1000;
total_rows INTEGER;
processed INTEGER := 0;
BEGIN
SELECT COUNT(*) INTO total_rows FROM old_table;
WHILE processed < total_rows LOOP
INSERT INTO new_table (id, data, created_at)
SELECT id, data, created_at
FROM old_table
WHERE id > processed
ORDER BY id
LIMIT batch_size;
processed := processed + batch_size;
RAISE NOTICE 'Processed % of % rows', processed, total_rows;
-- Allow other queries to run
PERFORM pg_sleep(0.1);
END LOOP;
END $$;-- up.sql
-- depends_on: 20250806140000_create_users_table
CREATE TABLE user_profiles (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users(id),
bio TEXT,
created_at TIMESTAMP DEFAULT now() NOT NULL
);# Rollback last migration
nself db migrate down
# Rollback multiple migrations
nself db migrate down 3
# Restore from backup (if needed)
nself db restore# Check what went wrong
nself logs postgres
# Check migration status
nself db migrate status
# Manual intervention if needed
nself db shell
# Repair migration tracking table
nself db migrate repair# Compare schema between environments
nself db schema diff staging
# Example output:
# Schema Differences (local vs staging)
# ────────────────────────────────────
# + Table: user_preferences (missing in staging)
# ~ Column: users.last_login (different type)
# - Index: idx_orders_date (missing in local)# When team members have conflicting migrations:
# 1. Coordinate with team
# 2. Merge schema.dbml files
# 3. Regenerate migrations
nself db schema import schema.dbml --name merged_changes
# 4. Test thoroughly before applying
nself db migrate status
nself db migrate up# Migration commands (v0.4.8)
nself db migrate status # Check migration status
nself db migrate up # Run pending migrations
nself db migrate up 3 # Run specific number
nself db migrate down # Rollback last migration
nself db migrate down 2 # Rollback specific number
nself db migrate create NAME # Create new migration
nself db migrate fresh # Drop all and re-run (dev only)
nself db migrate repair # Fix tracking table