TypeORM integration with migrations in NestJS
Tags: javascript, typescript, node, nestjs
Published: 2024-05-29 13:00:52

Love to work with you, You can hire me on [Upwork](https://www.upwork.com/freelancers/~011463d96b5b87d7ff).

To even think about TypeORM we must install all the required libraries in the application, already included command in getting started article. We can run below code to install TypeORM with pg Postgres driver.

```bash
npm install --save @nestjs/typeorm typeorm pg
```

We can define a factory class/function that will return all the required details/config for our TypeORM module. It will be helpful to segregate modules in terms of their functionality.

Even before everything we need a database, I have created nest-series database in Postgres using DBeaver. You can use PGAdmin or any other tool just connect to your local DB Or you can use remote as per your interest. After that You have to create a connection URL, that URL will look like below.

```ini
DATABASE_URL=postgresql://<username>:<password>@localhost:5432/nest-series
```

Just replace above with your credentials and place it in .env. Now we move to our config factory class. Config class will use details ormconfig.ts file, So ormconfig.ts can be used in npm command as data source.

```typescript
// ormconfig.ts

import { DataSource } from 'typeorm';
import * as dotenv from 'dotenv';
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';

dotenv.config();

export const configs: PostgresConnectionOptions = {
  type: 'postgres',
  url: process.env.DATABASE_URL,
  entities: [__dirname + '/src/**/*.entity.{ts,js}'],
  migrations: [__dirname + '/src/modules/database/migrations/*{.ts,.js}'],
  dropSchema: false,
  logging: false,
};
const dataSource = new DataSource(configs);

export default dataSource;
```


```typescript
// src/modules/database/typeorm.factory.ts

import { Injectable } from '@nestjs/common';
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';
import ORMConfig from '../../../ormconfig';

@Injectable()
export class TypeORMConfigFactory implements TypeOrmOptionsFactory {
  createTypeOrmOptions(): TypeOrmModuleOptions {
    return ORMConfig.options;
  }
}
```

We can use Injectable class to use configService as I have mentioned in earlier article that we will be using config service as an injected service. In constructor, we can inject configService as ConfigService. We have implemented TypeOrmOptionsFactory the interface to follow the particular contract of factory config class. Now we can use this factory in App Module file where we are registering our TypeORM Module.

```typescript
// src/modules/app/app.module.ts

...
import { TypeOrmModule } from '@nestjs/typeorm';
import { TypeORMConfigFactory } from '../database/typeorm.factory';

const modules = [];

export const global_modules = [
  ...
  TypeOrmModule.forRoot({
    useClass: TypeORMConfigFactory,
  }),
];

@Module({
  imports: [...global_modules, ...modules],
})
export class AppModule {}
```

After this, you can run npm run start:dev to confirm if anything is going wrong in setup for TypeORM. As we move forward, we need entities as per our requirement we have defined in the first article. We require entities, we will start from user entity But user also require base entity that will automatically handle the timestamps and ID for us.

```typescript
// src/entities/base.ts

import { ApiProperty } from '@nestjs/swagger';
import {
  BaseEntity as _BaseEntity,
  CreateDateColumn,
  DeleteDateColumn,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';

export abstract class BaseEntity extends _BaseEntity {
  // we are using uuid instead of integers
  @ApiProperty()
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @ApiProperty()
  @CreateDateColumn()
  created_at: Date;

  @ApiProperty()
  @UpdateDateColumn()
  updated_at: Date;

  @DeleteDateColumn()
  deleted_at: Date;
}
```

I have introduced swagger decorator above, You can see ApiProperty decorator that handles Swagger doc generation. deleted_at will not be exposed in swagger doc, So we are not adding decorator above for it. One little thing you might have noticed that I have created an abstract class, reason behind that is we don’t want any table related to Base entity or any other tracking from TypeORM. Now we move to user entity and user token entity.

```typescript
// src/entities/user.entity.ts

import { Column, Entity } from 'typeorm';
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
import { BaseEntity } from './base';

@Entity({ name: 'users' })
export class User extends BaseEntity {
  @ApiProperty({ example: 'Danimai' })
  @Column({ type: 'varchar', length: 50 })
  first_name: string;

  @ApiProperty({ example: 'Mandal' })
  @Column({ type: 'varchar', length: 50, nullable: true })
  last_name: string;

  @ApiProperty({ example: 'example@danimai.com' })
  @Column({ type: 'varchar', length: 255, unique: true })
  email: string;

  @ApiProperty({ example: 'Password@123' })
  @Column({ type: 'varchar', length: 255, nullable: true })
  password: string;

  @ApiHideProperty()
  @Column({ type: 'timestamp with time zone', nullable: true })
  email_verified_at: Date;

  @ApiHideProperty()
  @Column({ type: 'boolean', default: false })
  is_active: boolean;
}
```

```typescript
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
import { BeforeInsert, Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
import { User } from './user.entity';
import { BaseEntity } from './base';

export enum TokenType {
  REGISTER_VERIFY = 'REGISTER_VERIFY',
  RESET_PASSWORD = 'RESET_PASSWORD',
}

@Entity({ name: 'user_tokens' })
export class Token extends BaseEntity {
  @Column({ type: 'varchar', length: 100 })
  token: string;

  @Column({ type: 'boolean', default: false })
  is_used: boolean;

  @Column({ type: 'enum', enum: TokenType })
  type: TokenType;

  @Column({ type: 'timestamp' })
  expires_at: Date;

  @Column({ type: 'uuid' })
  user_id: string;

  @ManyToOne(() => User, (user) => user.tokens)
  @JoinColumn({ name: 'user_id' })
  user: User;

  // This decorator allows to run before insert command
  // setting up token automatically before insert
  @BeforeInsert()
  async generateToken() {
    // generate long token for registration and forgot password
    this.token = `${randomStringGenerator()}-${randomStringGenerator()}`;
  }
}
```

We also need something we called migration files to be generated for these entities So everyone can sync their DB. to do that we are going to add scripts for migration in package.json

```json
{
  ...
  "scripts": {
    ...
    "migration:generate": "typeorm-ts-node-commonjs migration:generate src/modules/database/migrations/migrations -d ormconfig.ts",
    "migration:run": "typeorm-ts-node-commonjs -d ormconfig.ts migration:run",
    "migration:revert": "typeorm-ts-node-commonjs -d ormconfig.ts migration:revert"
  },
  ...
}
```

After adding above command in package.json file we now can generate migration file using npm run migration:generate command and then to migrate them `npm run migration:run`, if we think that migration was wrong Or wanted to edit just revert the migration in DB by `npm run migration:revert`. So if you wanted to follow the migration, you can just follow me. Run below command.

```bash
npm run migration:generate
npm run migration:run
```

After running generate command, we will get a migration file in `src/modules/database/migrations` folder, mine is.

```typescript
// src/modules/database/migrations/1712332008837-migrations.ts

import { MigrationInterface, QueryRunner } from 'typeorm';

export class Migrations1712332008837 implements MigrationInterface {
  name = 'Migrations1712332008837';

  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(
      `CREATE TYPE "public"."user_tokens_type_enum" AS ENUM('REGISTER_VERIFY', 'RESET_PASSWORD')`,
    );
    await queryRunner.query(
      `CREATE TABLE "user_tokens" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "token" character varying(100) NOT NULL, "is_used" boolean NOT NULL DEFAULT false, "type" "public"."user_tokens_type_enum" NOT NULL, "expires_at" TIMESTAMP NOT NULL, "user_id" uuid NOT NULL, CONSTRAINT "PK_63764db9d9aaa4af33e07b2f4bf" PRIMARY KEY ("id"))`,
    );
    await queryRunner.query(
      `CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "first_name" character varying(50) NOT NULL, "last_name" character varying(50), "email" character varying(255) NOT NULL, "password" character varying(255), "email_verified_at" TIMESTAMP WITH TIME ZONE, "is_active" boolean NOT NULL DEFAULT false, CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`,
    );
    await queryRunner.query(
      `ALTER TABLE "user_tokens" ADD CONSTRAINT "FK_9e144a67be49e5bba91195ef5de" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
    );
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(
      `ALTER TABLE "user_tokens" DROP CONSTRAINT "FK_9e144a67be49e5bba91195ef5de"`,
    );
    await queryRunner.query(`DROP TABLE "users"`);
    await queryRunner.query(`DROP TABLE "user_tokens"`);
    await queryRunner.query(`DROP TYPE "public"."user_tokens_type_enum"`);
  }
}
```

And after `npm run migration:run` it should fill up your DB like mine

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hy3h31bibfevc2b6obcj.png)

As we are moving forward, we are arranging our structure, So the current structure looks pretty good and convincing in terms of clean code.
```bash
src
├── entities
│   ├── base.ts
│   ├── user.entity.ts
│   └── user_token.entity.ts
├── main.ts
└── modules
    ├── app
    │   └── app.module.ts
    ├── auth
    ├── config
    │   ├── app.config.ts
    │   ├── database.config.ts
    │   └── index.ts
    ├── database
    │   ├── migrations
    │   │   └── 1712332008837-migrations.ts
    │   └── typeorm.factory.ts
    └── user
```
If you run the `npm run start:dev` You will see below If having any doubts or issues, please mention them in comments.
```bash
[9:23:31 PM] Starting compilation in watch mode...

[9:23:33 PM] Found 0 errors. Watching for file changes.

[Nest] 26803  - 04/05/2024, 9:23:34 PM     LOG [NestFactory] Starting Nest application...
[Nest] 26803  - 04/05/2024, 9:23:34 PM     LOG [InstanceLoader] AppModule dependencies initialized +11ms
[Nest] 26803  - 04/05/2024, 9:23:34 PM     LOG [InstanceLoader] TypeOrmModule dependencies initialized +1ms
[Nest] 26803  - 04/05/2024, 9:23:34 PM     LOG [InstanceLoader] ConfigHostModule dependencies initialized +0ms
[Nest] 26803  - 04/05/2024, 9:23:34 PM     LOG [InstanceLoader] ConfigModule dependencies initialized +9ms
[Nest] 26803  - 04/05/2024, 9:23:35 PM     LOG [InstanceLoader] TypeOrmCoreModule dependencies initialized +183ms
[Nest] 26803  - 04/05/2024, 9:23:35 PM     LOG [NestApplication] Nest application successfully started +6ms
```
In the next article, we will be having our controllers and validation in place. So we can attach our Service in next series of articles.