PostgreSQL Schema Migrations with Schema Support
Postgres-schema-migrations is a JavaScript/TypeScript library for managing database schema changes in PostgreSQL. It is a fork of `postgres-migrations` that specifically adds support for schema namespaces, allowing separate migrations to be tracked per schema, which is beneficial for multi-tenant applications or reusing database code. Currently at version 7.0.2, this library is actively maintained. It mandates SQL files for migration definitions, ordered numerically, and deliberately omits 'down' migrations, advocating for 'rolling forward' with new migrations to reverse changes. A key differentiator is its emphasis on atomic transactions for each migration and hash-based checks to ensure migration immutability, preventing accidental changes to already-applied migrations. It supports Node.js 10.17.0+ and PostgreSQL 9.4+.
Common errors
-
Migration failed: Error: Migration file '5_add-table.sql' has been altered since it was applied. Aborting.
cause An SQL migration file that was previously successfully applied has been changed. The library stores a hash of the migration file content and detects mismatches.fixNever modify migration files after they have been run in any environment. If a change is needed, create a new migration file with a higher sequence number to apply the correction or new schema change. -
Error: Database "database-name" does not exist.
cause The target PostgreSQL database specified in the configuration does not exist, and `ensureDatabaseExists` was not set to `true` or was set to `false`.fixSet `ensureDatabaseExists: true` in your database configuration object passed to `migrate` if you want the library to create the database automatically. Alternatively, manually create the database before running migrations. -
Error: A migration with version '5' already exists ('5_add-existing-table.sql'). Cannot apply '5_add-new-feature.sql'.cause Two or more migration files share the same sequential version number in the migration directory, leading to a conflict in the execution order.fixRename one of the conflicting migration files to have a unique, higher sequential number. For example, if `5_add-table.sql` exists, rename `5_add-new-feature.sql` to `6_add-new-feature.sql`.
Warnings
- gotcha The library explicitly does not support 'down' migrations. Reversing a change requires a new 'forward' migration that undoes the previous effect. This is a deliberate design decision, not a bug.
- breaking Migration files are ordered numerically and are checked via a hash to ensure immutability. If a migration file is altered after being applied (e.g., to a production database), the migration system will detect the hash mismatch and refuse to apply further migrations, leading to an error.
- gotcha Concurrent migration file creation by multiple developers can lead to conflicts (e.g., two files named `5_add-table.sql` and `5_add-column.sql`). The system will detect this and refuse to apply.
- gotcha The `migrate` function's `ensureDatabaseExists` option defaults to `false` for backwards compatibility. If this option is not explicitly set to `true`, the target database must exist prior to running migrations, otherwise, the connection will fail. The `defaultDatabase` option also defaults to 'postgres' for database creation purposes.
- gotcha Certain DDL operations, like `CREATE INDEX` without `CONCURRENTLY` or most `ALTER TABLE` statements, can acquire strong locks that block reads and/or writes, potentially causing significant downtime on active production databases.
Install
-
npm install postgres-schema-migrations -
yarn add postgres-schema-migrations -
pnpm add postgres-schema-migrations
Imports
- migrate
const migrate = require('postgres-schema-migrations').migrateimport { migrate } from 'postgres-schema-migrations' - loadMigrationFiles
import loadMigrationFiles from 'postgres-schema-migrations/loadMigrationFiles'
import { loadMigrationFiles } from 'postgres-schema-migrations' - Client
import pg from 'pg'; const client = new pg.Client(...)
import { Client } from 'pg'
Quickstart
import { migrate } from "postgres-schema-migrations";
import { Client } from 'pg';
import path from 'path';
async function runMigrations() {
const migrationFilesPath = path.resolve(__dirname, 'migrations');
// Using a direct pg client allows more control over connection lifecycle
const dbConfig = {
database: process.env.DB_DATABASE ?? 'database-name',
user: process.env.DB_USER ?? 'postgres',
password: process.env.DB_PASSWORD ?? 'password',
host: process.env.DB_HOST ?? 'localhost',
port: parseInt(process.env.DB_PORT ?? '5432', 10),
};
const client = new Client(dbConfig);
await client.connect();
try {
console.log('Running migrations for public schema...');
await migrate({ client }, migrationFilesPath); // Defaults to 'public' schema
console.log('Public schema migrations complete.');
// Example of running migrations for a specific schema
console.log('Running migrations for custom_schema...');
const schemaMigrationsPath = path.resolve(__dirname, 'schema_migrations');
await migrate({ client }, schemaMigrationsPath, { schema: 'custom_schema' });
console.log('Custom schema migrations complete.');
} catch (error) {
console.error('Migration failed:', error);
process.exit(1);
} finally {
await client.end();
}
}
// Dummy migration files setup for example to be runnable
// In a real project, these would be actual .sql files.
// Make sure these directories exist for the quickstart to fully run.
// Example: migrations/1_create_users_table.sql
// CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL);
// Example: schema_migrations/1_create_products_table.sql
// CREATE SCHEMA IF NOT EXISTS custom_schema;
// CREATE TABLE custom_schema.products (id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL);
runMigrations();