NestJS with TypeORM, automatic migrations and reused CLI configuration

Here's a simple guide to setting up automated database migrations with TypeORM CLI in a NestJS project that I wish I had when I started.

  • It uses a ormconfig.ts file to configure the TypeORM CLI, which reuses the configuration from your application (no need to repeat yourself)
  • It will automatically find your entities, no build step required (unless you're running or generating migrations)
  • It will automatically bootstrap your database schema e.g. when developing locally
  • Your table names will be in snake_case and your TypeScript entities in camelCase
  • It uses the ConfigModule using the Configuration namespaces approach
  • It has package.json scripts to avoid having to specify the migrations directory every time you run a migration
  • It provides a simple workflow for generating migrations based on entity changes and the initial migration

Setup

Ensure you have the required dependencies

npm install typeorm @nestjs/typeorm typeorm-naming-strategies @nestjs/config

And the dev dependencies:

npm install cross-var ts-node --save-dev

Also be sure to add any database driver you need, e.g. pg for PostgreSQL.

Add migration scripts to package.json

It's a bit of some "magic" scripts to get the TypeORM CLI to work with your project setup inspired by this repo. Here's what you need to add to your package.json:

"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"
}

Create your database migartion configuration file

Create the following file: src/database/database.config.ts:

import { registerAs } from '@nestjs/config';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';

export default registerAs(
  'database',
  (): TypeOrmModuleOptions => ({
    type: 'cockroachdb',
    url: process.env.DATABASE_URL || 'postgresql://root:passwd@localhost:26257/defaultdb',
    autoLoadEntities: true,
    logging: process.env.NODE_ENV === 'development' || process.env.DATABASE_QUERY_LOGGING === '1',
    // In production, you might want to run migrations differently
    // (e.g. not automatically on app start)
    migrationsRun: true,
    migrations: ['dist/src/database/migrations/*.js'],
    namingStrategy: new SnakeNamingStrategy(),
    // Enable synchronize to auto-create tables, handy during initial development
    // Use migrations to manage database changes thereafter
    // (once you have a production database / initial release ready)
    // synchronize: process.env.NODE_ENV === 'development',
  }),
);

Adjust the connection URL and other options to match your database setup.

Import your database configuration in your app.module.ts

If you're using the ConfigModule with the Configuration namespaces approach, you can import your database configuration like this:

+import databaseConfig from './config/database.config';

@Module({
  imports: [
+    ConfigModule.forRoot({
+      load: [databaseConfig],
+    }),
+    TypeOrmModule.forRoot(databaseConfig()),

Create your ormconfig.ts file

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

import { ConfigModule } from '@nestjs/config';
import databaseConfig from 'src/config/database.config';
import { DataSource } from 'typeorm';

ConfigModule.forRoot({
  isGlobal: true,
  load: [databaseConfig],
});

export default new DataSource({
  ...databaseConfig(),
  entities: ['src/**/*.entity.ts'], // This is the important part
} as any);

The workflow

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.

Simply run the following command to generate the initial migration:

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

Migration 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:

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

Summary

I hope this guide helps you get started with setting up automated database migrations with TypeORM CLI in a NestJS project. It's a simple setup that I've found to be quite effective in my projects.

Let me know if you have any questions or suggestions in the comments below!

Comments