TypeORM PostgreSQL Migration Setup: Production-Ready Configuration

Topic: typeorm-postgres-migration-setupUpdated 7/14/2026

Quick Answer

  • Conclusion: TypeORM migrations provide a reliable, version-controlled way to manage PostgreSQL schema changes in production, replacing the unsafe synchronize: true approach.
  • First checks: Ensure synchronize: false in your data source config, install the pg driver with npm install pg, and define a migrations array pointing to your migration files.
  • Minimal command: Run npx typeorm migration:run -d ./path/to/data-source.ts after configuring your DataSource with host, username, password, database, and migrations paths.
  • Version boundary: This setup applies to TypeORM 0.3.x+ using the DataSource API; the legacy ormconfig.json approach is deprecated.

What Problem It Solves

TypeORM's synchronize: true automatically syncs your entity definitions to the database schema on every application launch. While convenient during prototyping, this is dangerous in production—it can drop columns, alter tables, or delete data without warning. Migrations solve this by providing explicit, versioned, and reversible schema change files that you review, test, and deploy deliberately.

Installation and Quick Start

Install the PostgreSQL driver:

BASH
npm install pg

Create a DataSource configuration file (e.g., data-source.ts):

TYPESCRIPT
import { DataSource } from "typeorm";

export const AppDataSource = new DataSource({
  type: "postgres",
  host: "localhost",
  port: 5432,
  username: "your_user",
  password: "your_password",
  database: "your_database",
  synchronize: false,
  migrations: ["src/migrations/*.ts"],
  migrationsTableName: "migrations",
});

Generate your first migration:

BASH
npx typeorm migration:generate src/migrations/InitialSchema -d ./data-source.ts

Run pending migrations:

BASH
npx typeorm migration:run -d ./data-source.ts

Minimal Working Configuration

The following DataSource configuration is the minimum required for a production-safe migration setup:

TYPESCRIPT
import { DataSource } from "typeorm";

export const AppDataSource = new DataSource({
  type: "postgres",
  host: process.env.DB_HOST || "localhost",
  port: parseInt(process.env.DB_PORT || "5432", 10),
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  synchronize: false,               // CRITICAL: must be false for migrations
  migrations: ["dist/migrations/*.js"],  // compiled migration files
  migrationsTableName: "migrations",     // stores migration history
});

Key points:

  • synchronize: false is mandatory when using migrations.
  • The migrations array accepts glob patterns; use dist/migrations/*.js for production and src/migrations/*.ts for development with ts-node.
  • migrationsTableName defaults to "migrations"; you can customize it if needed.

Parameters and Environment Variables

Essential Connection Parameters

ParameterRequiredDefaultDescription
hostYesDatabase host address
portNo5432Database host port
usernameYesDatabase username
passwordYesDatabase password
databaseYesDatabase name
urlNoConnection URL; other parameters override values from URL

Migration-Specific Parameters

ParameterRequiredDefaultDescription
synchronizeNofalseMust be false when using migrations
migrationsYesArray of migration classes or glob patterns
migrationsRunNofalseAuto-run migrations on app launch
migrationsTableNameNo"migrations"Table name for tracking executed migrations
migrationsTransactionModeNo"all"Transaction mode: "all", "none", or "each"

PostgreSQL-Specific Parameters

ParameterRequiredDefaultDescription
schemaNo"public"Database schema name
sslNoSSL/TLS configuration object
uuidExtensionNo"uuid-ossp"UUID generation extension ("uuid-ossp" or "pgcrypto")
connectTimeoutMSNoundefinedConnection timeout in milliseconds
poolErrorHandlerNowarn logHandler for pool error events
maxTransactionRetriesNo5Max retries for serialization failures (40001)
logNotificationsNofalseLog Postgres server notices and notifications
installExtensionsNotrueAuto-install required Postgres extensions
extensionsNoundefinedAdditional Postgres extensions to install
applicationNameNoundefinedApplication name visible in Postgres stats/logs
parseInt8NofalseParse int8 as JavaScript numbers (default: strings)

Root Cause Analysis

The most common migration failures stem from three root causes:

  1. Missing synchronize: false: If synchronize is true, TypeORM will attempt to sync the schema on every connection, potentially overwriting migration changes or causing conflicts.

  2. Incorrect migration path: The migrations array must point to compiled .js files in production. Using .ts paths with ts-node works in development but fails in production builds.

  3. Concurrent migration execution: Running migrations from multiple application instances simultaneously can cause race conditions, duplicate entries in the migrations table, or partial schema changes.

Common Errors and Fixes

ErrorCauseSolution
Error: Cannot connect to database. Connection refused.Database not running or incorrect connection parametersVerify host, port, username, password, database values; check firewall rules
Error: relation "migrations" does not existMigrations table missingTypeORM creates this table automatically on first run; ensure migrationsTableName matches your config
Error: Migration "MigrationName" has already been run.Duplicate migration executionCheck the migrations table; use typeorm migration:revert to roll back, or manually remove the record
QueryFailedError: duplicate key value violates unique constraintMigration tries to insert duplicate data or create existing indexReview migration SQL; clean conflicting data in development environments

Production Notes and Security Checks

Deployment Safety

  • Single-instance execution: Run migrations from only one instance (e.g., a Kubernetes Job or a deployment hook) to prevent concurrent conflicts.
  • Transaction mode: Set migrationsTransactionMode: "each" so each migration runs in its own transaction. If one fails, only that migration is rolled back.
  • Backup before migration: Always take a database backup before running migrations in production.

Security Hardening

  • Dedicated migration user: Create a PostgreSQL user with only the DDL permissions needed (CREATE TABLE, ALTER TABLE, etc.) and restrict access to other databases.
  • Credential management: Never hardcode credentials. Use environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault).
  • SSL enforcement: In production, configure ssl: { rejectUnauthorized: true } to enforce encrypted connections.

Performance Considerations

  • Split large migrations: Break large schema changes (e.g., adding indexes to large tables) into separate migration files.
  • Schedule during low traffic: Run migrations during maintenance windows or low-traffic periods.
  • Monitor execution: Enable logNotifications: true to capture Postgres server notices during migration execution.

FAQ

Q: How do I safely run TypeORM migrations in production?

A: Follow these best practices:

  1. Backup the database before running migrations.
  2. Set migrationsTransactionMode: "each" for per-migration transaction isolation.
  3. Run migrations during low-traffic periods.
  4. Execute from a single instance (Kubernetes Job, deployment hook).
  5. Enable detailed logging to monitor execution.
  6. Have a rollback plan ready using typeorm migration:revert.

Q: What are the advantages of TypeORM migrations over raw SQL migration files?

A: TypeORM migrations offer:

  1. Auto-generation: typeorm migration:generate detects entity-database differences and creates migration files automatically.
  2. TypeScript support: Write migrations with full type checking and IDE support.
  3. ORM integration: Migrations stay in sync with entity definitions.
  4. Flexible transaction control: Choose between "all", "each", or "none" transaction modes.
  5. Built-in rollback: The revert command handles rollbacks without manual scripts.
  6. Cross-database compatibility: Migration files work across supported databases (with database-specific syntax caveats).

Q: How do I handle migration conflicts when multiple developers generate migrations?

A: Best practices for conflict resolution:

  1. Timestamp naming: TypeORM uses timestamps in migration filenames, reducing naming collisions.
  2. Frequent sync: Pull latest code and run typeorm migration:run locally before generating new migrations.
  3. Code review: Review migration files during pull requests to verify order and correctness.
  4. Branch strategy: Generate migrations in feature branches; resolve conflicts when merging to main.
  5. Manual ordering: If migration file order conflicts, adjust timestamps in filenames to ensure correct execution sequence.

Related Guides