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 incamelCase
- 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
See this example repository for a demo.
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!