今回はRDBにMySQL、ORMにTypeORMをNestJS内で使用する方法を備忘録としてまとめていきます。Blogの登録機能として実装を進めていきます。
ゴール
- TypeORMを導入できていること
- MySQLがDockerで起動できていること
- Entityファイルに定義した内容からMySQLにテーブルが作成できていること
- 対象:blogsテーブル
※ 今回はテーブル作成までとして、次回以降で実際にCRUD機能を作っていきたいと思います
参考リンク
- NestJS – 公式Doc:https://docs.nestjs.com/techniques/database
- TypeORM – 公式Doc:https://orkhan.gitbook.io/typeorm/docs/migrations
開発環境
- VSCode
- 実行環境
- Node.js:
v16.x
- yarn:
v.1.22.x
- Node.js:
- ライブラリ
- @nestjs/cli:
8.2.x
- @nestjs/typeorm:
8.x
- typeorm:
0.2.x
- mysql2:
2.3.x
- @nestjs/cli:
導入手順
前提:nest new
コマンドによるプロジェクトフォルダができていること
- MySQLのDocker環境構築
- 必要ライブラリのインストール
- TypeORMの導入
- ormconfigファイル作成
- Entityの作成
- migrationsファイルの作成
- migrateの実行
- テーブルの確認
プロジェクトフォルダがまだできていない場合は下記記事にまとめていますので、参考にしてください。
実装
MySQLの環境構築
MySQL8系のDockerイメージを使用してMySQLを構築していきます
※ 一時的に利用するだけなので細かい設定は割愛
version: '3.8'
services:
db:
image: mysql:8.0
container_name: nestjs-mysql
volumes:
- db-store:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=sample
- MYSQL_DATABASE=sample
- MYSQL_USER=sample
- MYSQL_PASSWORD=sample
- TZ=Asia/Tokyo
ports:
- 3306:3306
# データを永続化させるため
volumes:
db-store:
MySQLの起動確認
$ docker-compose.yml up -d
$ docker-compose exec db bash -c 'mysql -u sample -p'
Enter password: sample
Welcome to the MySQL monitor. Commands end with ; or \\g.
Your MySQL connection id is 11
Server version: 8.0.28 MySQL Community Server - GPL
略
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| sample |
+--------------------+
2 rows in set (0.00 sec)
mysql>
無事起動&ログインできることを確認
必要ライブラリのインストール
$ yarn add @nestjs/typeorm typeorm mysql2
● 補足
typeorm:公式でもv0.3から大幅な変更が入ったため、公式通り0.2系をinstall
mysql2:2にしないと以前エラーになった記憶があったのでmysql2を選択
TypeORMの導入
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'sample',
password: 'sample',
database: 'sample',
synchronize: false,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
サーバー起動時にTypeOrmCoreModuleが無事にinitializedされていることを確認
LOG [InstanceLoader] TypeOrmCoreModule dependencies initialized
ormconfigファイル作成
forRoot()
に対しては、ormconfigファイルとして設定周りを抜き出し可能のため、
ormconfig.jsを作成する。
module.exports = {
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'sample',
password: 'sample',
database: 'sample',
synchronize: false,
};
//環境変数を使う場合は下記のようになる
//.envに必要な変数を定義
module.exports = {
type: 'mysql',
host: process.env.MYSQL_HOST,
port: process.env.MYSQL_POST,
username: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
synchronize: false,
};
NestJSでは@nestjs/configが内部的にdotenvを使用しているため、モジュールを読み込んで使うのが一般的っぽいですが一旦このまま進めます。
参考:https://docs.nestjs.com/techniques/database#async-configuration
app.module.tsが下記のようにスッキリしました。
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [TypeOrmModule.forRoot()],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
この状態でサーバー起動してエラーが出なければOK
Entityの作成
Blogテーブルを作るためのEntityを作成していきます
今回は練習がてらEnumやIndexなど色々使ってみます。
import { BlogStatus } from 'src/blogs/blog-status.enum';
import {
Entity,
Column,
Index,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('blogs') //テーブル名を指定
export class Blog {
@PrimaryGeneratedColumn()
id: number;
@Column({
nullable: false,
comment: '記事タイトル',
})
title: string;
@Column({
type: 'text',
nullable: true,
comment: '記事内容',
})
content: string;
@Column({
type: 'enum',
enum: BlogStatus,
default: BlogStatus.DRAFT,
comment: '公開状況',
})
status: BlogStatus;
@Index('createdAt-idx')
@CreateDateColumn()
readonly createdAt: string;
@UpdateDateColumn()
readonly updatedAt: string;
}
● 参考
- Column Options:https://orkhan.gitbook.io/typeorm/docs/entities#column-options
- Enum:https://orkhan.gitbook.io/typeorm/docs/entities#enum-column-type
@Entity('blogs')
でテーブル名を指定しないと、blogテーブルになってしまったのでoptionで指定
migrationsファイルの作成
先ほど作成したEntityファイルよりmigrationsファイルを生成していきます。
その前にormconfigの設定にentitiesやmigrationsの設定を追記します
module.exports = {
type: 'mysql',
host: process.env.MYSQL_HOST,
port: process.env.MYSQL_POST,
username: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
synchronize: false,
entities: ['dist/entities/*.entity.js'], //追記
migrations: ['dist/migrations/*.js'], //追記
cli: {
entitiesDir: './src/entities', //追記
migrationsDir: './src/migrations', //追記
},
};
●補足
- entitiesとmigrationsに実行対象のファイル群を指定
- cliに作成するファイルを配置するパスを指定
上記設定が完了したらmigrationsファイルを作成するコマンドを実行
引数にファイル名を指定して実行
$ npx typeorm migration:generate -n CreateBlog
参考:https://www.linkedin.com/pulse/nestjs-typeorm-migrations-nestor-iván-scoles
import {MigrationInterface, QueryRunner} from "typeorm";
export class CreateBlog1648475134277 implements MigrationInterface {
name = 'CreateBlog1648475134277'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE \\`blogs\\` (\\`id\\` int NOT NULL AUTO_INCREMENT, \\`title\\` varchar(255) NOT NULL COMMENT '記事タイトル', \\`content\\` text NULL COMMENT '記事内容', \\`status\\` enum ('1', '2') NOT NULL COMMENT '公開状況' DEFAULT '2', \\`createdAt\\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \\`updatedAt\\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), INDEX \\`createdAt-idx\\` (\\`createdAt\\`), PRIMARY KEY (\\`id\\`)) ENGINE=InnoDB`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX \\`createdAt-idx\\` ON \\`blogs\\``);
await queryRunner.query(`DROP TABLE \\`blogs\\``);
}
}
上記が作成できたらMySQLにテーブルを作成するためにmigrateを実行
#migrate実行
$ npx typeorm migration:run
#migrateのロールバック
$ npx typeorm migration:revert
実行が完了するとテーブルにblogsテーブルが作成されていることが確認できます。
※ SequelAceで確認
※ indexも無事作成できていました
さいごに
今回はTypeORMを使ってテーブル作成まで実装することができました。
Entity内でデコレータを使えば、型やカラムの設定も簡単にできたため非常に使いやすいなと感じました。
次は今回作成したテーブルを使って、CRUD機能を作っていきたいと思います。
ご覧いただきありがとうございました。