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:
- The CLI cannot find any of your entities. Make sure that the entities array in your
ormconfig
is correct. - 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
. - Make sure you are running the command against a clean database (drop and recreate the database).
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.