Zero-dependency SQL migration runner. Plain .sql files, database-agnostic, with CLI support.
You bring your own database client. The library handles ordering, tracking, and execution.
npm install migrate-sqlimport { createMigrator } from 'migrate-sql'
const migrator = createMigrator({
directory: './migrations',
table: '_migrations', // optional, default: '_migrations'
})
// Provide a simple adapter wrapping your database client
const adapter = {
query: (sql, params) => pool.query(sql, params).then(r => r.rows)
}
await migrator.up(adapter) // run all pending migrations
await migrator.down(adapter) // rollback last applied migration
await migrator.status(adapter) // { applied: [...], pending: [...] }# Uses DATABASE_URL environment variable
npx migrate-sql up --dir ./migrations
npx migrate-sql down --dir ./migrations
npx migrate-sql status --dir ./migrations
# Or pass the URL directly
npx migrate-sql up --url postgresql://user:pass@localhost/mydbThe CLI requires pg for PostgreSQL connections. Install it separately:
npm install pgPlace plain SQL files in your migrations directory with this naming convention:
migrations/
001_create_users.up.sql
001_create_users.down.sql
002_add_email.up.sql
002_add_email.down.sql
003_create_indexes.up.sql
Rules:
- Files must follow the pattern
NNN_name.up.sql/NNN_name.down.sql - Every migration needs an
.up.sqlfile .down.sqlfiles are optional (but required for rollback)- Migrations run in numeric order
import pg from 'pg'
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL })
const adapter = {
query: async (sql, params) => {
const result = await pool.query(sql, params)
return result.rows
}
}import Database from 'better-sqlite3'
const db = new Database('app.db')
const adapter = {
query: async (sql, params) => {
if (sql.trim().toUpperCase().startsWith('SELECT')) {
return db.prepare(sql).all(...(params ?? []))
}
db.prepare(sql).run(...(params ?? []))
return []
}
}| Option | Type | Default | Description |
|---|---|---|---|
directory |
string |
required | Path to migration files |
table |
string |
'_migrations' |
Tracking table name |
Returns an object with:
up(adapter)— Run pending migrations. Returns{ applied: string[], skipped: string[] }down(adapter)— Rollback last migration. Returns{ applied: string[], skipped: string[] }status(adapter)— Returns{ applied: string[], pending: string[] }
interface DatabaseAdapter {
query(sql: string, params?: unknown[]): Promise<unknown[]>
}Low-level function to scan and parse migration files without executing anything.
import { scanMigrations } from 'migrate-sql'
const migrations = scanMigrations('./migrations')
// [{ number: 1, name: 'create_users', id: '001_create_users', upPath, downPath }]MIT