Database migrations are an essential part of modern application development, allowing developers to manage changes to their database schema in a controlled and versioned manner. When building applications with NestJS and TypeORM, handling these migrations effectively is crucial for ensuring data integrity, enabling seamless collaboration among team members, and facilitating deployments across different environments. This guide will walk you through the process of setting up and managing database migrations in your NestJS project using TypeORM, focusing on best practices for a smooth development workflow.

Step 1: Download and Set Up NestJS
If you don't have the NestJS CLI installed globally, begin by installing it using npm or yarn:

npm install -g @nestjs/cli
# or
yarn global add @nestjs/cli

Once the CLI is installed, create a new NestJS project:

nest new project-name

Navigate into your newly created project directory:

cd project-name

Step 2: Install Dependencies for TypeORM with NestJS
You need to install the necessary packages to integrate TypeORM with your NestJS application, specifically for MySQL. This includes the @nestjs/typeorm package, typeorm itself, and the mysql2 database driver. We'll also keep reflect-metadata and typeorm-extension as they are generally useful.

npm install @nestjs/typeorm typeorm mysql2 reflect-metadata typeorm-extension
# or
yarn add @nestjs/typeorm typeorm mysql2 reflect-metadata typeorm-extension

Explanation of dependencies:

  • @nestjs/typeorm: Provides the NestJS module for integrating TypeORM.

  • typeorm: The core TypeORM library.

  • mysql2: The MySQL driver.

  • reflect-metadata: A required dependency for TypeORM to work with - TypeScript decorators.

  • typeorm-extension: Provides helpful utilities for TypeORM, including enhanced migration capabilities.

Step 3: Configure TypeORM in Your NestJS Application

Now that you have the dependencies installed, the next step is to configure the TypeOrmModule in your NestJS application. We'll create a dedicated data-source.ts file at the root of your project (or in a designated configuration directory) and define your TypeORM configuration there.
Create data-source.ts:
Create a file named data-source.ts in the root of your project directory and add the following content:

// data-source.ts
import { DataSource } from 'typeorm';
import { join } from 'path'; // Import join

export default new DataSource({
  type: 'mysql',
  host: 'localhost',
  port: 3306,
  username: 'root', // Your database username
  password: 'admin', // Your database password
  database: 'test', // Your database name
  entities: [
    // Specify the path to your entities relative to this file
    join(__dirname, '**/*.entity{.ts,.js}'),
  ],
  migrations: [
    // Specify the path to your migration files relative to this file
    join(__dirname, 'migrations/**/*{.ts,.js}'),
  ],
  migrationsRun: false,
  synchronize: true, // IMPORTANT: Set to false for production
});

Explanation of data-source.ts:

  • We import DataSourcefrom typeorm.
  • We create and export a new DataSource() instance with your MySQL connection details and paths to your entities and migrations.
  • The entitiesand migrationspaths are defined relative to the location of the data-source.ts file using join(__dirname, ...).
  • We set migrationsRun: false to prevent migrations from running automatically on application start.
  • Crucially, set synchronize: false for production environments. While synchronize: true can be convenient during initial development to quickly create tables based on your entities, it's dangerous in production as it can lead to data loss when schemas change. Migrations are the safe and controlled way to manage schema evolution in production.

Update AppModule:

Now, in your src/app.module.ts, you will import this dataSourceand use its options to configure the TypeOrmModule.

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

By using ...dataSource.options, you are spreading the configuration defined in data-source.ts into the TypeOrmModule.forRoot() method. This ensures consistency between the configuration used by your NestJS application and the TypeORM CLI.

Now that your TypeORM configuration is set up using a separate data-source.ts file, you are ready to configure the TypeORM CLI and start creating your database migrations.

Step 4: Configurepackage.json Scripts for Migrations
To streamline the process of creating, generating, and running migrations, you should add scripts to your package.json file. These scripts will use the TypeORM CLI with the typeorm-ts-node-commonjs wrapper to execute commands directly on your TypeScript files, leveraging the data-source.tsconfiguration we created.

Open your package.json file and add the following scripts within the scripts block:

{
  "scripts": {
    "typeorm": "npx typeorm-ts-node-commonjs",
    "migration:create": "npm run typeorm migration:create ./src/migrations/%npm_config_name%",
    "migration:generate": "npm run typeorm migration:generate -d ./data-source.ts -n %npm_config_name%",
    "migration:run": "npm run typeorm migration:run -d ./data-source.ts",
    "migration:revert": "npm run typeorm migration:revert -d ./data-source.ts"
  },
  // ... other package.json configurations
}

Explanation of the Scripts:

  • "typeorm": "npx typeorm-ts-node-commonjs": This is a helper script that sets up the execution environment for TypeORM commands, allowing it to run TypeScript files directly. We use npx to execute the package binary without needing to install it globally.
  • "migration:create": "npm run typeorm migration:create ./src/migrations/%npm_config_name%": This script creates a new, empty migration file in the src/migrations directory. It uses %npm_config_name%to get the name you provide when running the command (e.g., npm run migration:create --name=CreateUsersTable).
  • "migration:generate": "npm run typeorm migration:generate -d ./data-source.ts -n %npm_config_name%": This script is more powerful. It generates a migration file by comparing your current database schema with your TypeORM entities. It figures out the necessary SQL commands (CREATE TABLE, ALTER TABLE, etc.) to make the database schema match your entities and writes them into a new migration file. It uses -d ./data-source.ts to specify the data source configuration and -n %npm_config_name% for the migration name.
  • "migration:run": "npm run typeorm migration:run -d ./data-source.ts": This script executes all pending migrations that have not yet been applied to the database specified in your data-source.ts.
  • "migration:revert": "npm run typeorm migration:revert -d ./data-source.ts": This script reverts the last applied migration. This is useful during development if you need to undo a recent schema change.

Difference between migration:createand migration:generate:

  • migration:create: Manual approach. Creates an empty migration file. You then manually write the up and downmethods with the SQL or TypeORM query builder commands to perform the desired schema changes.

  • migration:generate: Automatic approach. Compares entities to the database and automatically generates the SQL commands needed to synchronize the schema into the migration file's upand downmethods. You should always review the generated SQL to ensure it does exactly what you intend.

🚨 Environment Variable Syntax Alert (Windows vs. Linux/macOS) 🚨
The scripts provided above use%npm_config_name%, which is the syntax for accessing npm configuration variables (like the --name flag) on Windows.

If you are using Linux ** or **macOS, the syntax for accessing these variables is different. You should use $npm_config_name instead.

Here are the equivalent scripts for Linux / macOS:

{
  "scripts": {
    "typeorm": "npx typeorm-ts-node-commonjs",
    "migration:create": "npm run typeorm migration:create ./src/migrations/$npm_config_name",
    "migration:generate": "npm run typeorm migration:generate -d ./data-source.ts -n $npm_config_name",
    "migration:run": "npm run typeorm migration:run -d ./data-source.ts",
    "migration:revert": "npm run typeorm migration:revert -d ./data-source.ts"
  },
  // ... other package.json configurations
}

Make sure to use the script format that matches your operating system.

With these scripts configured, you can now easily manage your TypeORM migrations from your terminal using npm run . The next step will cover how to create your first migration.

Step 5: Creating our first migration

Important: You must create a root project a folder with name "migrations", this will call when you runnig all your migrations

Now that yourpackage.json scripts are set up, you can create your first migration file. We'll use the migration:create script for this, which creates an empty migration file ready for you to define the schema changes manually.

Open your terminal in the project's root directory and run the following command, with a descriptive name for your migration:

npm run migration:create --name=CreateUsersTable
# or
yarn migration:create --name=CreateUsersTable

This command will create a new file in the src/migrations directory (as configured in your data-source.ts and package.json). The filename will typically follow the pattern -.ts. For example, 1745031524856-CreateUsersTable.ts.

Open this newly created file. You will find a class that implements the MigrationInterfacefrom TypeORM. This interface requires two methods: upand down.

  • The up method contains the logic to apply the schema changes (e.g., create tables, add columns).
  • The down method contains the logic to revert those changes (e.g., drop tables, remove columns). This is important for rolling back migrations if needed. Here's an example of what the generated file looks like, populated with the SQL to create a userstable as you provided:
import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateUsersTable1745031524856 implements MigrationInterface {

    // The 'up' method applies the migration
    public async up(queryRunner: QueryRunner): Promise<void> {
        // Use queryRunner.query to execute raw SQL commands
        await queryRunner.query(`
            CREATE TABLE users (
                id INT PRIMARY KEY AUTO_INCREMENT,
                name VARCHAR(255) NOT NULL,
                lastname VARCHAR(255) NOT NULL,
                email VARCHAR(255) UNIQUE NOT NULL,
                birth DATE NOT NULL,
                created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            );
        `);
    }

    // The 'down' method reverts the migration
    public async down(queryRunner: QueryRunner): Promise<void> {
        // Use queryRunner.query to execute raw SQL commands to drop the table
        await queryRunner.query(`DROP TABLE IF EXISTS users`);
    }
}

In this example:

  • In the upmethod, we use queryRunner.query() to execute a raw SQL statement that creates a userstable with several columns, including id, name, lastname, email, birth, created_at, and updated_at. In the downmethod, we use queryRunner.query() to execute a raw SQL statement that drops the userstable if it exists.

When usingmigration:create, you are responsible for writing the SQL or using TypeORM's QueryRunner methods to define the schema changes precisely in both the upand downmethods. This gives you fine-grained control over your database schema evolution.

The next step will be to run this migration to apply the changes to your database.

Step 6: Running Your Migrations
Once you have created your migration file and defined the schema changes in the upmethod (and the corresponding rollback in the downmethod), you need to execute the migration to apply these changes to your database.

Use the following command in your terminal:

npm run migration:run
# or
yarn migration:run

What this command does:

  • Connects to the Database: The script uses the configuration defined in your data-source.ts file to establish a connection to your MySQL database.
  • Checks for Pending Migrations: TypeORM queries a special table (usually named typeorm_metadata) in your database to see which migrations have already been executed.
  • Executes Pending Migrations: It then runs the upmethod of any migration files that exist in your configured migrationsdirectory but have not yet been recorded as executed in the typeorm_metadatatable.
  • Records Executed Migrations: After successfully running a migration's upmethod, TypeORM records that migration in the typeorm_metadatatable, ensuring it won't be run again.

Keeping Track of Migrations:

TypeORM automatically manages the state of your migrations in the database. The typeorm_metadatatable stores the names of the migrations that have been applied, allowing TypeORM to know which migrations are pending.

Checking Migration Status (Optional but Recommended):

You can check the status of your migrations at any time using the TypeORM CLI. While we didn't explicitly add a script for this, you can run:

npm run typeorm migration:show -d ./data-source.ts
# or
yarn typeorm migration:show -d ./data-source.ts

This command will list all migrations and indicate whether they have been applied (X) or are pending ([ ]).

Reverting Migrations (for Development/Rollback):

As mentioned in Step 4, you can revert the last applied migration using:

npm run migration:revert
# or
yarn migration:revert

This executes the downmethod of the most recently applied migration and removes its entry from the typeorm_metadatatable. Use this cautiously, especially in environments with live data.

By following these steps, you can effectively manage your database schema changes in your NestJS application using TypeORM migrations. Remember to create a new migration file for every change to your database schema.

Wrap Up!
So, there you have it! Taming database changes in your NestJS app with TypeORM migrations isn't scary once you break it down. We got you hooked up with TypeORM, set up that cool data-source.ts file, dialed in some handy scripts in your package.json, and even cranked out and ran your very first migration.

Think of migrations as your database's personal diary – keeping track of every little change. It makes life way easier when you're working with a team or pushing updates live.

Seriously, get into the habit of using migrations for any database tweak. It's a game-changer for keeping things smooth and preventing headaches down the road.

Wanna keep up with more dev tips, tricks, and maybe some random tech thoughts? Hit me up on social media!

Threads: @brngranado
LinkedIn: @brngranado
Instagram: @brngranado
Swing by and say hi! And definitely stick around for the next blog post – got more cool stuff coming your way!