TypeORM database migrations in NestJS apps

In this article we look at how to do database migrations using TypeORM and NestJS. For people who don’t know these frameworks: NestJS is an opinionated server-side application development framework built on top of Node.js; TypeORM is an ORM framework like Hibernate and can be used with Typescript and Javascript. This combination has become quite popular nowadays for building back-end apps. Developers who have experience in Angular, SpringBoot would almost feel home when building apps with these frameworks.

First, lets create a scaffolding for our NestJS app with the following commands:

1.git clone https://github.com/nestjs/typescript-starter.git nestjs-typeorm-migration-demo2.cd nestjs-typeorm-migration-demo3.npm install4.npm install --save @nestjs/typeorm typeorm pg

We use Postgres for this demo but feel free to use any other RDBMS: just relevant node driver dependency need to be installed. Now lets configure TypeORM to connect to the database. There are several ways do this. We go with creating a file with name ‘ormconfig.json’ directly under project root folder like below:

[
{
"name": "default",
"type": "postgres",
"host": "localhost",
"port": 5432,
"username": <username>,
"password": <password>,
"database": "demo",
"entities": ["dist/**/*.entity{ .ts,.js}"],
"synchronize": false,
"migrations": ["dist/migrations/*{.ts,.js}"],
"migrationsTableName": "migrations_typeorm",
"migrationsRun": true
}
]

One thing that possibly can be done in the above config is to get values of synchronize and migrationsRun from environment variables. This way, in develop branch, TypeORM can be used to automatically push schema to database from your entities and in upstream environments migrations can be used . After this step modify app.module.ts file under src directory like below to enable TypeORM integration.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [TypeOrmModule.forRoot()],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

Next, lets create an entity Article(article.entity.ts) under src directory like below:

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Article {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
content: string;
}

We now need to write migrations for the Article entity. Lets create a directory with name migrations under src directory and execute below command from command line:

npx typeorm migration:create -n ArticleTable -d src/migrations

The above command generates a migration file with name like <timestamp>-ArticleTable.ts. Basically, whenever we start our application, TypeORM runs all listed migrations which haven’t been run before. It maintains this information in the meta table(migrations_typeorm in our case) that we configure in ormconfig.json. The migration file primarily contains two methods:up and down; up should contain the code that we need to perform the migration and down should revert what up changes. Migration can be written in plain SQL using queryRunner.query(‘’) method or using migration API. We use migration API for up query and SQL for down query.

import { MigrationInterface, QueryRunner, Table } from 'typeorm';
export class ArticleTable1586653096209 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.createTable(
new Table({
name: 'article',
columns: [
{
name: 'id',
type: 'int4',
isPrimary: true,
isGenerated: true,
generationStrategy: 'increment',
},
{
name: 'title',
type: 'varchar',
isNullable: false,
},
{
name: 'content',
type: 'varchar',
isNullable: false,
},
],
}),
false,
);
}
public async down(queryRunner: QueryRunner): Promise<any> {
queryRunner.query(`DROP TABLE article`);
}
}

Finally lets build and start our app with the following commands:

1.npm run build
2.npm start

After the application is started we can see that the article table is created in the database and the corresponding migration entry in the migrations_typeorm table. Awesome! Now lets say after going into production we would like to add one more column — author — to the article entity. To do this we just need to create one more migration entry and write code for adding the column.

import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddColToArticleTable1586666091775 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
queryRunner.query(`ALTER TABLE article ADD author VARCHAR(30)`);
}
public async down(queryRunner: QueryRunner): Promise<any> {
queryRunner.dropColumn('article', 'author');
}
}

If we restart our application, our newly created migration is run and the new column is attached to the article table — simple. However, writing migrations manually could be cumbersome in some cases. TypeORM can automate this process. To demonstrate, delete the above created migrations files from the project and article table from the database. After that run the following commands:

1.npx typeorm migration:generate -n ArticleTable -d src/migrations
2.npm run build
3.npm run start

The above step generates a create table migration script based on our article entity and when we start the app, migration is run and the table is created in the database. Now lets rename title column in article entity to name and run the below commands:

1.npm run build
2.npx typeorm migration:generate -n RenameColInArticleTable -d src/migrations

We can observe that TypeORM automatically detects the renamed column and generates relevant migration script. Here the idea is to generate migrations after changes to entities. So far we have let migrations run automatically whenever the applications starts. However, if required, we could also run/revert migrations from the command line as well with the below commands:

1.typeorm migration:run
2.typeorm migration:revert

As we have seen, migrations can provide a clean way to automate database schema changes where all changes are properly version controlled. Hopefully this post is useful. Please let know your feedback in comments.

All the above code can be found at https://github.com/anjithp/nestjs-typeorm-migration-demo

engineer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store