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やマイグレーションファイルのクラスを指定するのは面倒なので、ワイルドカードで指定できるようにしたい。