Database migrations are essential for managing database schemas (tables, columns, relationships) and data instances (records inside tables) in a safe and controlled manner. Each migration consists of two methods:
- up - applies actual change
- down - reverts the change made by the up method
Many NestJs developers struggle with setting up migrations correctly. In this article, we will walk through creating, running and managing migrations in NestJs using TypeORM with PostgresSQL, using practical, step by step examples.
Prerequisites
Before starting, make sure you have the following:
- Basic knowledge of NestJs
- NodeJs 18+
- NestJs 10+
- Basic knowledge of TypeORM
- Basic knowledge of PostgresSQL
Project Setup
We will create a minimal NestJs application to demonstrate migrations step by step.
1. Create a New NestJs Application
Run the following command to create a new NestJs project:
nest new nestjs-migration
2. Configure TypeORM
Create a config folder in the project root and create a file named typeorm.config.ts.
Install the required packages (TypeORM, PostgresSQL driver and configuration loader):
npm i @nestjs/typeorm @nestjs/config typeorm pg
Now paste the following code to config/typeorm.config.ts:
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { DataSource, DataSourceOptions } from 'typeorm';
ConfigModule.forRoot({ isGlobal: true });
const configService = new ConfigService();
const DB_PORT = configService.getOrThrow<number>('DATABASE_PORT');
export const typeOrmConfig: TypeOrmModuleOptions = {
type: 'postgres',
host: configService.getOrThrow<string>('HOST_NAME'),
port: DB_PORT,
username: configService.getOrThrow<string>('DATABASE_USERNAME'),
password: configService.getOrThrow<string>('DATABASE_PASSWORD'),
database: configService.getOrThrow<string>('DATABASE_NAME'),
entities: [__dirname + '/../**/*.entity.{js,ts}'],
migrations: [__dirname + '/../src/migrations/*.{js,ts}'],
synchronize: true,
};
export const dataSource = new DataSource(
typeOrmConfig as DataSourceOptions,
);
3. Environment Variables
Create a .env file in the project root and add the following values (replace them with your own credentials).
Make sure to create a PostgresSQL database named nestjs_migration.
HOST_NAME=localhost
DATABASE_PORT=5432
DATABASE_USERNAME=your_username
DATABASE_PASSWORD=your_password
DATABASE_NAME=nestjs_migration
4. Create entity
Create a file named user.entity inside the src folder:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('user')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
password: string;
}
5. Register TypeORM in AppModule
Update app.module.ts as:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { typeOrmConfig } from 'config/typeorm.config';
import { User } from './user.entity';
@Module({
imports: [
TypeOrmModule.forRoot(typeOrmConfig),
TypeOrmModule.forFeature([User]),
],
})
export class AppModule {}
6. Add TypeORM Migration Scripts
Add the following scripts to the scripts section of package.json.
Linux / macOS
"typeorm": "ts-node ./node_modules/typeorm/cli",
"migration:run": "npm run typeorm migration:run -- -d ./config/typeorm.config.ts",
"migration:generate": "npm run typeorm -- -d ./config/typeorm.config.ts migration:generate ./src/migrations/$npm_config_name",
"migration:create": "npm run typeorm -- migration:create ./src/migrations/$npm_config_name",
"migration:revert": "npm run typeorm -- -d ./config/typeorm.config.ts migration:revert"
Windows
"typeorm": "ts-node ./node_modules/typeorm/cli",
"migration:run": "npm run typeorm migration:run -- -d ./config/typeorm.config.ts",
"migration:generate": "npm run typeorm -- -d ./config/typeorm.config.ts migration:generate ./src/migrations/%npm_config_name%",
"migration:create": "npm run typeorm -- migration:create ./src/migrations/%npm_config_name%",
"migration:revert": "npm run typeorm -- -d ./config/typeorm.config.ts migration:revert"
7. Initial Database Sync
Run the application to create the initial user table:
npm run start:dev
Ensure synchronize:true is enabled in the database configuration at this stage.
Understanding Migration Commands
1. typeorm
"typeorm": "ts-node ./node_modules/typeorm/cli"
This command allows us to run the TypeORM CLI using ts-node, enabling the execution of Typescript files without building the project. All migrations rely on this command.
2. migration:generate
Before generating migrations, set synchronize to false to prevent TypeORM from automatically applying schema changes.
Let us add a new username field to User entity:
@Column()
username: string;
Now generate a migration:
npm run migration:generate --name=username-field-addition
The command generates migration similar to:
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UsernameFieldAddition1768070224042
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user" ADD "username" character varying NOT NULL`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user" DROP COLUMN "username"`,
);
}
}
3. migration:run
npm run migration:run
This command executes all pending migrations by running their up methods. TypeORM tracks executed migrations in a dedicated table to prevent re-runnig them.
In order to apply the changes in the generated migration above, we need to execute it. Hence, run the migration:run command to add username in user table in the database.
4. migration:revert
npm run migration:revert
This runs the down method of the migration, reverting the changes. After reverting, delete the migration file to avoid reapplying it later.
If we run the migration:revert command above, the username field will be removed.
5. migration:create
The command creates empty migration for manual changes, commonly used for inserting initial data (e.g. a superadmin user account)
npm run migration:create --name=superadmin-user
The above migration creates a migration with the name superadmin-user. You can use any name you want.
Generated migration
import { MigrationInterface, QueryRunner } from 'typeorm';
export class SuperadminUser1768073019893
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {}
public async down(queryRunner: QueryRunner): Promise<void> {}
}
Modify the above migration manually to insert superadmin user:
import { MigrationInterface, QueryRunner } from 'typeorm';
export class SuperadminUser1768073019893
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`
INSERT INTO "user" (email, password)
VALUES ($1, $2)
ON CONFLICT (email) DO NOTHING
`,
['superadmin@example.com', 'superadmin123'],
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`
DELETE FROM "user"
WHERE email = $1
`,
['superadmin@example.com'],
);
}
}
Run the migration:
npm run migration:run
The superadmin user record will now exist in the database.
Conclusion
Database migrations are powerful tools for managing both the schema changes and data updates safely. This article demonstrated a practical and straightforward approach to handling migrations in NestJS with TypeORM, from setup to advanced use cases.
Contact
If you have any questions, feel free to reach out:
- LinkedIn: https://linkedin.com/in/dawit-girma-7b8867228/
- Email: realdavis7779@gmail.com
- Github: https://github.com/dedawit
The final code is available at public repository:
https://github.com/dedawit/nestjs-migration.git
References
https://docs.nestjs.com/
https://typeorm.io/docs/migrations/why
Top comments (0)