How to setup TypeORM migrations in a NestJS project
Complete database migration setup from scratch in a NestJS project using TypeORM
Motivation
TypeORM is the most mature Object Relational Mapper (ORM) for TypeScript and JavaScript. Since it's written in TypeScript, it integrates well with the NestJS framework. For integrating with SQL and NoSQL databases, Nest provides the @nestjs/typeorm
package. Here the official documentation of NestJS has clear instructions on how to integrate TypeORM with it. However, they skipped the details on how to setup the migrations and forwarded them to the TypeORM documentation for migration, which is very generic and not aligned with NestJS. That is the motivation of this article. In this article, I'll show how to setup migrations in a NestJS project using TypeORM. Buckle up and let's get started.
Initial project setup
Let's initialize our project using Nest CLI.
nest new nest-typeorm
Navigate to the project folder and install the necessary dependencies. In this example, I am using PostgreSQL, therefore I have added pg
as a dependency.
cd nest-typeorm
npm install --save @nestjs/typeorm typeorm pg
Database connection
Let's create a separate Nest module for handling database-related tasks.
nest g module db
Create the data-source.ts
file inside the src/db
directory. This file will contain database connection configurations.
// src/db/data-source.ts
import { config } from 'dotenv';
import { DataSourceOptions } from 'typeorm';
config();
export const dataSourceOptions: DataSourceOptions = {
type: 'postgres',
host: process.env.DB_HOST,
port: +process.env.DB_PORT,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [],
};
Use this dataSourceOptions
for setting up TypeOrmModule inside the DbModule that we generated using Nest CLI.
// src/db/db.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { dataSourceOptions } from './data-source';
@Module({
imports: [TypeOrmModule.forRoot(dataSourceOptions)],
})
export class DbModule {}
Now if you run the project using npm run start:dev
command, it should be able to connect to the database. However, you have to make sure the database server is up and running and the database with the name that we specified in the data-source
file is already created.
Generate an entity
Generate users
resource using the CLI.
nest g resource resource/users
It will generate the users
module, controller, service, DTOs and entity file inside src/resource/users
directory.
Let's update the user.entity.ts
file and add some columns there.
// src/resources/users/entities/user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'first_name' })
firstName: string;
@Column({ name: 'last_name' })
lastName: string;
}
Now we need to create the migration for creating the user table with the id, first_name, last_name columns.
Generate the migration
Migration in TypeORM is just a class that implements MigrationInterface
from the typeorm
package. It needs to implement the up
and down
methods. The up
method executes the query for applying new changes in the database. On the other hand, down
method executes the query to revert the changes that was applied by the up
method.
It is possible to implement a migration by manually implementing the MigrationInterface
. However, TypeORM provides a nice CLI to generate that automatically based on the data-source and entity.
Now the first thing is, we need to make TypeORM aware that we have an entity called user
. To do that, we need to update the entities
array in the data-source file. The entities array accepts both entity classes and directories from where entities need to be loaded. Directories support glob pattern. In our case, we want every entity from our resources directory to be loaded.
// src/db/data-source.ts
...
export const dataSourceOptions: DataSourceOptions = {
...
entities: ['src/resources/**/*.entity.ts'],
};
Though we are working with typescript, the above code will throw Cannot use import statement outside a module
error. To make it work smoothly it is safe to use .js
files. Our build command will generate the javascript files during build time and put them into /dist
directory. We can simply use that path here.
// src/db/data-source.ts
...
export const dataSourceOptions: DataSourceOptions = {
...
entities: ['dist/resources/**/*.entity.js'],
};
At this point, our project is ready for generating the first migration. We will use TypeORM CLI to generate the migration file. The command for generating a migration file from a data-source is like this.
npx typeorm migration:generate -d path/to/datasource path/to/Migration
However, we are using entity path from dist
directory. That means, we need to run the build command before using the typeorm command. We can combined those commands like this.
npm run build && npx typeorm migration:generate -d path/to/datasource path/to/Migration
Let's add a script in our package.json
file combining these commands.
// package.json
{
...
"scripts": {
...
"migration:generate": "npm run build && npx typeorm migration:generate -d ./dist/db/data-source.js",
}
}
Notice that, we have added the path to data-soure file in the script but we omitted the path to migration file. We want to pass that dynamically from the command line when we invoke the script.
Let's invoke the script.
npm run migration:generate src/db/migrations/create-user-table
Once we execute this command in the terminal it will generate the migration file in the src/db/migrations
directory. The file name will be something like <timestamp>-create-user-table.ts
If you open the migration file you can see the code like below.
// src/db/migrations/<timestamp>-create-user-table.ts
import { MigrationInterface, QueryRunner } from "typeorm";
export class CreateUserTable1690462207991 implements MigrationInterface {
name = 'CreateUserTable1690462207991'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "user" ("id" SERIAL NOT NULL, "first_name" character varying NOT NULL, "last_name" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "user"`);
}
}
Run the migration
The migration file is generated. Now we need to execute it to update our database structure.
Before using the migration:run
command from TypeORM CLI we need to update the dataSourceOptions
and add migrations
array to tell TypeORM from where it should read the migration files.
// src/db/data-source.ts
...
export const dataSourceOptions: DataSourceOptions & SeederOptions = {
...
migrations: ['dist/db/migrations/*.js'],
};
We have used the glob pattern to pick migration files from the dist
directory (similar to entities
array that we added before). Therefore, we must build the project before running the migration command. Same as before, we can combine the build and migration commands in the package.json
file.
Let's add the script to run the migration in our package.json
file.
// package.json
{
...
"scripts": {
...
"migration:run": "npm run build && npx typeorm migration:run -d ./dist/db/data-source.js",
}
}
Run the script through the terminal.
npm run migration:run
If you check your database now, you can see the user
table is created.
Conclusion
We have learned how to set up migration in a NestJS project using TypeORM.
You can find the full project here:
https://github.com/mazid1/nestjs-typeorm/tree/feature/migration
In the next article, I will write about how to seed test data using TypeORM.
I'm open for a remote full-time/contract/freelance job for the position of Full-stack Software Engineer. If you are hiring, ping me at mazidmailbox@gmail.com