はじめに
node.jsのフレームワークで最近勢いのあるnestjsと、これまた人気が出てるORMのTypeORMを触ってみました。
node.jsのフレームワークは薄いものが多いですが、それらとは違って結構多機能で便利に感じたので、備忘録がてら記事にします。
typescript、 eslint、 prettier、 jest、 ホットリロードなどが初めから設定された状態で開発できる
nest new <project-name>
とするだけで初めからいろんなツールチェインが入った状態で開発ができます。
あれを入れて、これを入れて、、とやる手間が省けて楽でした。
class-validatorを使ってリクエストボディのバリデーションが書ける
nestjsではリクエストで飛んでくるボディやクエリストリングなどのことをDTOと読んでます。
このDTOはクラスで定義するのですが、そのクラスのプロパティにclass-validatorのデコレータをペタペタ追加していくことで、柔軟なバリデーションをかけることができます。
class CreateParams {
@IsNotEmpty()
hoge: string;
}
@Controller('sample')
export class SampleController {
constructor(private sampleService: SampleService) {}
// これで、「POST /sample」ではbody内にhoge情報が必須になる
@Post()
create(@Body() params: CreateParams) {
return this.sampleService.create(params);
}
}
またこのバリデーションを有効にするためには以下の記述が必要でした。
参考: https://docs.nestjs.com/pipes
app.useGlobalPipes(new ValidationPipe());
swaggerを書くのが簡単
swaggerの定義をymlで書くのって割とめんどくさいと思っちゃうんですが、これもデコレーターを使って簡単に書けます。
こんな感じの定義をしておけば、
export class FindByIdParams {
@ApiProperty({
description: 'sample_idでの検索',
type: Number,
})
@IsNumberString()
sample_id: number;
}
他の書き方とかの詳細はこちら: https://docs.nestjs.com/recipes/swagger#openapi-swagger
トランザクションを貼りたい
nestjsとTypeORMのカスタムリポジトリを使っている状態でトランザクションを貼るにはどうやるのがいいのか探していたらこの記事が見つかりました。
こんな感じでトランザクションを貼れました。
@Injectable()
export class SampleService {
constructor(private sampleRepository: SampleRepository, private connection: Connection) {}
create(params: CreateParams): Promise<Sample> {
return this.connection.transaction(async (manager) => {
const sampleRepository = manager.getCustomRepository(SampleRepository);
return sampleRepository.save({
...params,
created_at: new Date(),
updated_at: new Date(),
});
});
}
}
TypeORMはトランザクションを貼るにもいろんな方法を提供してくれてます。(3種類くらい?)
ただ、nestjsの方の記事で見つけたのですが、 @Transaction
のデコレータを使ってtransactionを貼るのはおすすめしないみたいです。

TypeORMのマイグレーションが便利
TypeORMはコードの状態とDBの状態を比較して、そのコードの状態にするためのマイグレーションファイルを自動で作ってくれます。
例えば、こういうsamplesのentityを定義するコードがあるとして、DB上にはsamplesテーブルがなかった場合、
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('samples')
export class Sample {
@PrimaryGeneratedColumn()
id: number;
@Column()
hoge: string;
@Column()
created_at: Date;
@Column()
updated_at: Date;
}
samplesをCREATEするマイグレーションファイルを自動で作ってくれます。
import {MigrationInterface, QueryRunner} from "typeorm";
export class CreateSamples1584670197350 implements MigrationInterface {
name = 'CreateSamples1584670197350'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "samples" ("id" SERIAL NOT NULL, "hoge" character varying NOT NULL, "created_at" TIMESTAMP NOT NULL, "updated_at" TIMESTAMP NOT NULL, CONSTRAINT "PK_d68b5b3bd25a6851b033fb63444" PRIMARY KEY ("id"))`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "samples"`, undefined);
}
}
このCREATE TABLEの実行後に、entityにカラム追加をした場合は、
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('samples')
export class Sample {
@PrimaryGeneratedColumn()
id: number;
@Column()
hoge: string;
@Column()
huga: string; // 追加
@Column()
created_at: Date;
@Column()
updated_at: Date;
}
差分となるALTER TABLEのマイグレーションファイルを自動で作ってくれます。
import {MigrationInterface, QueryRunner} from "typeorm";
export class AddHugaColumnToSamples1584682435530 implements MigrationInterface {
name = 'AddHugaColumnToSamples1584682435530'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "samples" ADD "huga" character varying NOT NULL`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "samples" DROP COLUMN "huga"`, undefined);
}
}
また、TypeORMの設定項目の synchronize
を有効にしておくと、コードのentityに何か変更があった場合、マイグレーションを自動実行してくれるようになるので、開発中にマイグレーション周りの作業をほとんどしなくてよくなります。
最後に
他にもnestjsは色々機能があるので、いいと思ったのがあれば加筆していきます。
今回試してみたコードはこちらにあげてます。
https://github.com/pokotyan/nestjs-sample