TypeORM CLIを使ってマイグレーションをするまで

色々と格闘したので備忘録。(まだ完全には解決していない)

CLIプロジェクトの作成

npx typeorm init --docker

プロジェクトが作成された。

Dockerの起動

README.mdにコマンドが記載されているので、Dockerの起動までを実行。1つのターミナルをずっと使う場合はバックグラウンドで実行されるようにdオプションをつける。

npm i
docker-compose up -d

Dockerが起動した。まだプロジェクトは起動させない。

Entityの追加

src/entityフォルダにはデフォルトでUserテーブルが存在しているが、追加で2つほどEntityを作成する。ユーザが勉強を記録できるアプリをイメージして、教材テーブルと勉強履歴テーブルを追加。

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

/**
 * 教材テーブル
 */
@Entity()
export class Material {

    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string

}
import { Entity, Column, JoinColumn, PrimaryColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm"
import { Material } from "./Material"
import { User } from "./User"

/**
 * 勉強履歴テーブル
 */
@Entity()
export class StudyRecord {
    @PrimaryGeneratedColumn()
    id: number

    @ManyToOne(() => User)
    @JoinColumn({
        name: 'user_id',
        referencedColumnName: 'id',
    })
    readonly user: User

    @ManyToOne(() => Material, {nullable: true})
    @JoinColumn({
        name: 'material_id',
        referencedColumnName: 'id',
    })
    readonly material?: Material

    @Column()
    minutes: number
}

DataSourceの設定をいじる

srcフォルダ内にDBの接続設定を定義しているdata-source.tsというファイルがある。ここを一部いじる。

synchronizeのオフ

これがtrueになっていると、DBのInitializeがされたときにエンティティと実際のテーブルを自動的に同期してしまう。今回は手動でマイグレーションをやりたいのでオフにしておく。

entitiesに追加

デフォルトでUserが指定されているが、MaterialとStudyRecordも追加しておく。

※後述するが、ここは未解決部分

ここまででDataSourceはこんな感じになる。

import "reflect-metadata"
import { DataSource } from "typeorm"
import { Material } from "./entity/Material"
import { StudyRecord } from "./entity/StudyRecord"
import { User } from "./entity/User"

export const AppDataSource = new DataSource({
    type: "postgres",
    host: "localhost",
    port: 5432,
    username: "test",
    password: "test",
    database: "test",
    synchronize: false,
    logging: false,
    entities: [
        User,
        Material,
        StudyRecord,
    ],
    migrations: [],
    subscribers: [],
})

マイグレーションファイルを作成する

プロジェクトにある3つのエンティティをデータベースに作成するためのSQL文を自動生成する。

npm run typeorm migration:generate .\src\migration\Initial -- -d .\src\data-source.ts

src/migrationフォルダ内に「タイムスタンプ-Initial.ts」というファイルが作成される。

中身はこのようになっている。

import { MigrationInterface, QueryRunner } from "typeorm";

export class Initial1679806856972 implements MigrationInterface {
    name = 'Initial1679806856972'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`CREATE TABLE "material" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_0343d0d577f3effc2054cbaca7f" PRIMARY KEY ("id"))`);
        await queryRunner.query(`CREATE TABLE "user" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "age" integer NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`);
        await queryRunner.query(`CREATE TABLE "study_record" ("id" SERIAL NOT NULL, "minutes" integer NOT NULL, "user_id" integer, "material_id" integer, CONSTRAINT "PK_df4e710a6ba302ae3ab0cba6786" PRIMARY KEY ("id"))`);
        await queryRunner.query(`ALTER TABLE "study_record" ADD CONSTRAINT "FK_21fed8754ccbfab8b28d4e3fb23" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
        await queryRunner.query(`ALTER TABLE "study_record" ADD CONSTRAINT "FK_a2687c1d098666eaad59f3ad5d1" FOREIGN KEY ("material_id") REFERENCES "material"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "study_record" DROP CONSTRAINT "FK_a2687c1d098666eaad59f3ad5d1"`);
        await queryRunner.query(`ALTER TABLE "study_record" DROP CONSTRAINT "FK_21fed8754ccbfab8b28d4e3fb23"`);
        await queryRunner.query(`DROP TABLE "study_record"`);
        await queryRunner.query(`DROP TABLE "user"`);
        await queryRunner.query(`DROP TABLE "material"`);
    }

}

マイグレーションを実行する

マイグレーションファイルが作成できたので、この内容を実際にDBに反映させる。

まずsrc/data-source.tsを再度修正する。変更点はmigrationsのところに先ほど自動生成したマイグレーションファイルのクラス名を指定したこと。

※ここも未解決部分

import "reflect-metadata"
import { DataSource } from "typeorm"
import { Material } from "./entity/Material"
import { StudyRecord } from "./entity/StudyRecord"
import { User } from "./entity/User"
import { Initial1679806856972 } from "./migration/1679806856972-Initial"

export const AppDataSource = new DataSource({
    type: "postgres",
    host: "localhost",
    port: 5432,
    username: "test",
    password: "test",
    database: "test",
    synchronize: false,
    logging: false,
    entities: [
        User,
        Material,
        StudyRecord,
    ],
    migrations: [
        Initial1679806856972,
    ],
    subscribers: [],
})

そしてマイグレーションを実行する。

npm run typeorm migration:run -- -d .\src\data-source.ts

ターミナルにこんな風にずらずらとSQLの実行履歴が表示される。

SQLクライアントで見てみると、テーブルが作成されていることがわかる。

プロジェクトの起動

ようやくここでプロジェクトを起動してみる。

npm run start

このようにデータベースにレコードが追加され、ユーザテーブルの内容が表示されれば起動成功。

未解決部分について

src/data-source.tsを、最初は以下のようにしていた。

import "reflect-metadata"
import { DataSource } from "typeorm"

export const AppDataSource = new DataSource({
    type: "postgres",
    host: "localhost",
    port: 5432,
    username: "test",
    password: "test",
    database: "test",
    synchronize: false,
    logging: false,
    entities: [
        "src/entity/*.ts",
    ],
    migrations: [
        "src/migration/*.ts",
    ],
    subscribers: [],
})

これだとなぜかentitiesとmigrationsの中身が認識されず(?)マイグレーションファイルの作成やマイグレーションの実行ができなかった。(src/entity/*.tsではなくentity/*.tsなど色々変えてみたが)

ここがまだ解決できていないので今後調査していく。

いちいちEntityやマイグレーションファイルのクラスを指定するのは面倒なので、ワイルドカードで指定できるようにしたい。

コメントする

メールアドレスが公開されることはありません。