Setup automated Database Migrations with TypeORM CLI

I found myself struggling to find a simple guide on how to set up automated database migrations with the TypeORM CLI. It can be quite overwhelming to get started, despite the fact that it's a very common task and seemingly simple task.

I was looking for simple answers to the following questions:

  • How do I generate the initial migration automatically (based on the created entities - when bootstrapping a new project)?
  • How do I avoid having to specify the migrations directory every time I run a migration?
  • What does the workflow look like?

It turns out it's quite simple, if everything is correctly configured, but the documentation is a bit scattered and the CLI is lacking proper error/warning messages in my experience. So here's a simple guide to setting up automated database migrations with TypeORM that I wish I had when I started.

Setup

1. Ensure you have a ormconfig.json / ormconfig.ts file in your project root

This file is used to configure the TypeORM CLI which you'll be using to generate and run migrations.

2. Ensure that the entities array in your ormconfig is configured (!)

This is the most important part. The CLI uses this to generate migrations based on the entities you've defined. The CLI will provide no warnings if it cannot find any entities.

Setup might vary depending on your framework, but my ormconfig.ts looks like this:

import { DataSource } from 'typeorm';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; // Optional: I like snake_case for my tables and camelCase for my TypeScript entities

export default new DataSource({
  // Your database connection options here
  entities: ['src/**/*.entity.ts'], // This is the important part
  namingStrategy: new SnakeNamingStrategy(), // Optional
});

3. Setup package.json scripts to run the migrations

To reduce the amount of commands I need to remember to run and write, I found this great example of how to setup some basic migration commands to avoid having to specify the migrations directory params.

It uses cross-var and ts-node so remember to have that installed.

{
  "scripts": {
    "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli -d ormconfig.ts",
    "migration:create": "cross-var ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli migration:create ./src/database/migrations/$npm_config_name",
    "migration:generate": "cross-var npm run typeorm -- migration:generate ./src/database/migrations/$npm_config_name",
    "migration:run": "npm run build && npm run typeorm -- migration:run",
    "migration:revert": "npm run typeorm -- migration:revert"
  },
  "devDependencies": {
    "cross-var": "^1.1.0",
    "ts-node": "^10.4.0"
  }
}

Adjust the src/database/migrations path to match your project structure.

The workflow

Now that you have the basic setup, below is a simple workflow for generating and running migrations.

Initial migration

This was the part that I found the most confusing. When you're bootstrapping a new project, you want to generate the initial migration based on the entities you've created.

Once you have created your entities, you can run the following command to generate the initial migration:

npm run migration:generate --name=initial-db

It will generate a migration file in your src/database/migrations directory, called something like 1722856393104-initial-db.ts, that looks something like this:

import { MigrationInterface, QueryRunner } from 'typeorm';

export class InitialDb1722856393104 implements MigrationInterface {
  name = 'InitialDb1722856393104';

  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
        CREATE TABLE "user"
        (
            "id"        UUID                 DEFAULT gen_random_uuid() NOT NULL,
            "name"      varchar     NOT NULL,
            "createdAt" timestamptz NOT NULL DEFAULT now(),
            "updatedAt" timestamptz NOT NULL DEFAULT now(),
            CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id")
        )
    `);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP TABLE "user"`);
  }
}

Important: The CLI will not warn you if it cannot find any entities. So if you run this command and nothing happens, make sure that the entities array in your ormconfig is correct.

If you were to run npm run migration:generate --name=some-migration without having changed any entities (after having run the initial migration, of course), you would get the error No changes in database schema were found - cannot generate a migration.

Running migrations

You can run your migrations with:

npm run migration:run

Let's go ahead and test if it can generate a migration based on the entities we've changed.

Generating migrations from entity changes

Let's say you've changed a column name in your entity:

@Entity()
export class User {
  @Column()
-  name: string;
+  fullName: string;
}

You can now run the following command to generate a migration based on the changes you've made:

npm run migration:generate --name=change-user-column

It will generate a migration file in your src/database/migrations directory, called something like 1722856393104-change-user-column.ts, that looks something like this:

import { MigrationInterface, QueryRunner } from 'typeorm';

export class ChangedNameColumn1722859765490 implements MigrationInterface {
  name = 'ChangedNameColumn1722859765490';

  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`ALTER TABLE "user" RENAME COLUMN "name" TO "full_name"`);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`ALTER TABLE "user" RENAME COLUMN "full_name" TO "name"`);
  }
}

Manually creating migrations

If you want to create a new migration manually, you can run the following command:

npm run migration:create --name=my-new-migration

This will create a new migration file in your src/database/migrations directory, called something like 1722856393104-my-new-migration.ts, with an empty up and down method.

import { MigrationInterface, QueryRunner } from 'typeorm';

export class MyNewMigration1722859913782 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    // Your migration code here
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    // Your migration code here
  }
}

Troubleshooting

No changes in database schema were found

If you're getting: No changes in database schema were found - cannot generate a migration. To create a new empty migration use "typeorm migration:create" command., theres at least two possible reasons for this error:

  1. The CLI cannot find any of your entities. Make sure that the entities array in your ormconfig is correct.
  2. You haven't changed any entities since the last migration. If you want to create a new empty migration, you can run npm run migration:create --name=my-new-migration.

Unable to open file: ormconfig.ts

Ensure that the path to your ormconfig.ts file is correct.

Summary

I hope this simple guide helps you get started with setting up automated database migrations with TypeORM CLI. It's a simple setup that I've found to be quite effective in my projects. If you have any questions or suggestions, feel free to comment below!

I've also written a simple post on setting up TypeOrm with a NestJS project, which you can find here.

Comments