NestJS × TypeORM 0.3 でCLIからmigrationする

前提

下記記事の構成で環境構築した前提で進める。

NestJS(Fastify) × TypeORM × PostgreSQL × Dockerで環境構築

手順

1. Userエンティティの実装

Terminal window
nest g res users --no-spec
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes

まずCRUD generatorでusersリソースを追加する。

(※本記事で使用するのはuser.entity.tsのみなので、その他に作成されたuser.controller.tsuser.service.ts、DTOなどは、クラスの中身を空にするなり削除するなりして問題ない)

import {
Entity,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
Timestamp,
Column,
} from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
readonly id?: number;
@CreateDateColumn({ name: 'create_at' })
readonly createdAt?: Timestamp;
@UpdateDateColumn({ name: 'updated_at' })
readonly updatedAt?: Timestamp;
@Column({ unique: true })
email: string;
@Column({ name: 'hashed_password' })
hashedPassword: string;
}

user.entity.tsにて、上記のようにカラムを設定する。

2. データソースの設定

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';
export const ENTITIES_DIR = 'dist/**/*.entity.js';
export const MIGRATION_FILES_DIR = 'dist/database/migrations/*.js';
@Module({
imports: [
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get<string>('DATABASE_HOST'),
database: configService.get<string>('DATABASE_NAME'),
username: configService.get<string>('DATABASE_USER'),
password: configService.get<string>('DATABASE_PASSWORD'),
port: Number(configService.get<string>('DATABASE_PORT')),
entities: [],
entities: [ENTITIES_DIR],
synchronize: false,
migrations: [MIGRATION_FILES_DIR],
}),
}),
],
})
export class DatabaseModule {}

今回作成していくデータソースファイルだが環境構築で作成したsrc/database/database.module.tsと重複する設定があるため、上記のように修正する。

  • 先ほど定義したUserEntityを指定しつつ、変数化してexportする
  • migrationsにマイグレーションファイルのパスを指定
import { DataSource } from 'typeorm';
import { ENTITIES_DIR, MIGRATION_FILES_DIR } from './database.module';
import 'dotenv/config';
export const AppDataSource = new DataSource({
type: 'postgres',
host: process.env.DATABASE_HOST,
database: process.env.DATABASE_NAME,
username: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
port: Number(process.env.DATABASE_PORT),
entities: [ENTITIES_DIR],
synchronize: false,
migrations: [MIGRATION_FILES_DIR],
});

データソースファイルであるsrc/database/database-source.tsを作成し、先ほどexportしたものをこちらでも指定する。

3. マイグレーションファイルの作成

Terminal window
docker exec -it app sh
Terminal window
mkdir -p src/database/migrations

コンテナ内に入り、専用のディレクトリを作成。

Terminal window
npx typeorm-ts-node-commonjs migration:generate -d src/database/database-source.ts --pretty src/database/migrations/CreateUser

上記コマンドを叩く。(--prettyを付与し、作成されるマイグレーションファイル内のSQL文を複数行にしている)

import { MigrationInterface, QueryRunner } from "typeorm";
export class CreateUser1695999831504 implements MigrationInterface {
name = 'CreateUser1695999831504'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE "user" (
"id" SERIAL NOT NULL,
"create_a" TIMESTAMP NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
"email" character varying NOT NULL,
"hashed_password" character varying NOT NULL,
CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id")
)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
DROP TABLE "user"
`);
}
}

src/database/migrations/xxxxxxxxxxxxx-CreateUser.tsが作成され、今回追加したUserのテーブルをCREATEするSQLが出力されていることが確認できる。

4. マイグレーションの実行

Terminal window
npx typeorm-ts-node-commonjs migration:run -d src/database/database-source.ts

上記コマンドを叩く。

query: SELECT * FROM current_schema()
query: SELECT version();
query: SELECT * FROM "information_schema"."tables" WHERE "table_schema" = 'public' AND "table_name" = 'migrations'
query: CREATE TABLE "migrations" ("id" SERIAL NOT NULL, "timestamp" bigint NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_8c82d7f526340ab734260ea46be" PRIMARY KEY ("id"))
query: SELECT * FROM "migrations" "migrations" ORDER BY "id" DESC
0 migrations are already loaded in the database.
1 migrations were found in the source code.
1 migrations are new migrations must be executed.
query: START TRANSACTION
query:
CREATE TABLE "user" (
"id" SERIAL NOT NULL,
"create_a" TIMESTAMP NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
"email" character varying NOT NULL,
"hashed_password" character varying NOT NULL,
CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id")
)
query: INSERT INTO "migrations"("timestamp", "name") VALUES ($1, $2) -- PARAMETERS: [1695999831504,"CreateUser1695999831504"]
Migration CreateUser1695999831504 has been executed successfully.
query: COMMIT

先ほど作成したマイグレーションファイルに沿ってクエリが実行された。

Userテーブルが作成されカラムも定義されている

pgAdminを見てみると、usersテーブルが作成されカラムも定義されていることが確認できる。

123

参考
  1. Migrations - typeorm

  2. NestJS+TypeORM 0.3系でマイグレーションを実行する - Zenn

  3. TypeORMのスキーママイグレーションを使う | 豆蔵デベロッパーサイト