はじめに
Nest.js + TypeORM についての 備忘録 です。
環境
Nest V8 なので古いと思いますが、modular architecture を学習する分には大丈夫かと思います。
TypeORM V2 なのでmigration周りが少し違うようです。
いつか書き直します・・・
NestJSのスケルトン作成
・nest new ${プロジェクト名} で作成する。
・ex) flea-market というプロジェクト名で作成する。
nest new flea-market
Nestの3つのコア要素
・Module -> Controller と Service をまとめて Nestに登録する 役割を持つ
・Controller -> ルーティングする 役割を持つ
・Service -> 実装したいロジックを定義する 役割を持つ
3つのコア要素を使って エンドポイントを作る。
Module概要
・ロジックとルーティングをまとめる 論理的な構造 というイメージ
1.class に Moduleデコレーター をつける。
2.Moduleデコレーターの引数 に オブジェクト を渡す。
3.オブジェクトのプロパティ は
providers -> Injectableデコレータ がついたクラスを渡す。
controllers -> Controllerデコレータ がついたクラスを渡す。
imports -> モジュールの中で使う外部のモジュール を渡す。
exports -> 外部のモジュールに エクスポートしたいもの を渡す。
@Module({
imports: [ItemsModule],
controllers: [],
providers: [],
})
Module作成コマンド
・nest g module ${モジュール名} で作成する。
・ex) items というモジュール名で作成する。
nest g module items
Controller概要
・ルーティングの機能 を担っている。
1.class に Controllerデコレーター をつける。
2.Controllerデコレーターの引数 に 文字列 を渡す。
これが パス と コントローラー との紐づけになる。
3.Controllerデコレーター と紐づけた classのメソッド に
HTTPメソッドデコレーター をつける。
@Controller('items')
export class ItemsController {
@Get()
findAll() {
return 'これはfindAllメソッドです。';
}
}
Controller作成コマンド
・nest g controller ${コントローラー名} で作成する。
・ex) items というコントローラー名で作成する。
nest g controller items
nest g controller items --no-spec //テスト用のファイルを作成しない
Service概要
・実装したいロジックを定義する 役割を持つ
・コントローラー から呼び出して、ルーティングと機能 を紐づける。
1.class に Injectableデコレーター をつける。
2.Injectableデコレーター と紐づけた classのメソッド に
実装したいロジックをメソッドとして 定義する。
@Injectable()
export class ItemsService {
findAll() {
return 'これはItemsServiceです。';
}
}
ControllerからServiceを利用する
1.Module の providers に Service を登録する。
これにより Nestが勝手にDI してくれるようになる。
@Module({
controllers: [ItemsController],
providers: [ItemsService],
})
export class ItemsModule {}
2.Controllerの中にconstructor で Serviceを引数 に取る。
これにより Controller から Service を利用出来る。
@Controller('items')
export class ItemsController {
constructor(private readonly itemsService: ItemsService) {}
@Get()
findAll() {
return this.itemsService.findAll();
}
}
Service作成コマンド
・nest g service ${サービス名} で作成する。
・ex) items というコントローラー名で作成する。
nest g service items
nest g service items --no-spec // テスト用のファイルを作成しない
DI(Dependency Injection/依存性の注入)について
クラスAがクラスBに依存している 時に、
Aの中でBをインスタンス化せずに、
Aの外 で Bをインスタンス化 し、Aに渡して実装します。
このように 外部から依存しているオブジェクト を 渡すパターン を DI と呼ぶ。
ダイナミックルーティング + ルーティングパスの取得
・HTTPデコレータ に対して
:${パス}の文字列 を渡すと ダイナミックルーティング が出来る。
・ハンドラの引数に
Paramデコレータの引数に渡したダイナミックルーティングのパス と
パスの文字列 を引数とすることで取得出来る。
・/items/${id} への GETリクエスト の例を下記に示します。
@Controller('items')
export class ItemsController {
constructor(private readonly itemsService: ItemsService) {}
// ダイナミックルーティング
@Get(':id')
findById(@Param('id') id: string): Item {
return this.itemsService.findById(id);
}
}
Post送信のBodyを取得する
・ハンドラの引数に Bodyデコレータ と 引数 を渡して取得する。
・/items への POSTリクエスト の例を下記に示します。
@Controller('items')
export class ItemsController {
constructor(private readonly itemsService: ItemsService) {}
@Post()
create(
@Body('id') id: string,
@Body('name') name: string,
@Body('price') price: number,
@Body('description') description: string,
): Item {
const item: Item = {
id,
name,
price,
description,
status: ItemStatus.ON_SALE,
};
return this.itemsService.create(item);
}
}
DTO(Data Transfer Object)について
・データの受け渡しに使われるオブジェクトのこと
・ex)DBとモデル間 でのデータやり取り・リクエストオブジェクト からのデータ受け取り
DTOの作り方
・ex) CreateItemDto というDTOの定義を下記に示します。
export class CreateItemDto {
id: string;
name: string;
price: number;
description: string;
}
DTOを使ってBodyを取得する
・Post送信のBodyを取得する ではデコレータに引数を渡す必要がありますが、
DTOを使ってBodyを取得する時はデコレータに引数を渡す必要がありません。
下記に DTOを使ったBody取得 の例を示します。
@Controller('items')
export class ItemsController {
constructor(private readonly itemsService: ItemsService) {}
@Post()
create(@Body() CreateItemDto: CreateItemDto): Item {
return this.itemsService.create(CreateItemDto);
}
}
Nestでのバリデーション実装
・Nestでのバリデーションには pipe を使います。
・pipeでは バリデーション・データの変換 が可能です。
バリデーション用の組み込み関数
・ValidationPipe -> 入力のバリデーション
・ParseIntPipe -> 入力を整数型に変換する
・ParseBoolPipe -> 入力をBoolean型に変換する
・ParseUUIDPipe -> 入力をUUID型に変換する
・DefaultValuePipe -> 入力がnull・undefinedの時にデフォルト値を与える
バリデーションの適用箇所
・ハンドラ に対して適用する
・パラメータごと に対して適用する
・グローバル に対して適用する
・ex) パラメータごと に対してバリデーションを適用する例を下記に示します。
@Controller('items')
export class ItemsController {
constructor(private readonly itemsService: ItemsService) {}
// ダイナミックルーティング
@Get(':id')
findById(@Param('id', ParseUUIDPipe) id: string): Item {
return this.itemsService.findById(id);
}
}
DTOにバリデーションを実装
・バリデーションルール はDTOに実装します。
import { Type } from 'class-transformer';
import { IsInt, IsNotEmpty, IsString, MaxLength, Min } from 'class-validator';
export class CreateItemDto {
@IsString()
@IsNotEmpty()
@MaxLength(40)
name: string;
@IsInt()
@Min(1)
@Type(() => Number)
price: number;
@IsString()
@IsNotEmpty()
description: string;
}
・バリデーションは グローバルに適用 します。
async function bootstrap() {
// 引数に渡したものがルートモジュールになる
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
レスポンスにビルトインされている例外処理を使う
・ダイナミックルーティングされた値 をキーにDBからレコードを取得して、
レスポンスとして返すようなAPIを想定します。
・NotFoundException はビルトインされた例外処理であり、
そのまま使用することが出来ます。
その実装例を下記に示します。
@Injectable()
export class ItemsService {
private items: Item[] = [];
findById(id: string): Item {
const found = this.items.find((item) => item.id === id);
if (!found) {
throw new NotFoundException();
}
return found;
}
}
・また、ビルトインされた例外処理は公式から見ることが出来ます。
TypeORM
・Entity -> テーブルに対応するクラス
・Repository -> DB操作に対応するクラス
TypeORMをNestにインストールする
・TypeOrmModule をインポートして適用する。
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'postgres',
autoLoadEntities: true,
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
Entityの作成方法
・Entityデコレータ をつけたクラスを定義して、その中にカラム名を記述する。
import { ItemStatus } from 'src/items/item-status.enum';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Item {
// 主キーかつ自動採番であることを示すデコレータ
@PrimaryGeneratedColumn('uuid')
id: string;
// 通常のカラムであることを示すデコレータ
@Column()
name: string;
@Column()
price: number;
@Column()
description: string;
@Column()
status: ItemStatus;
@Column()
createdAt: string;
@Column()
updatedAt: string;
}
Entityを使ってmigrationする
- /ormconfig.js でexportする
- migrationファイル を作成する
- migration を実行する
module.exports = {
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'postgres',
autoLoadEntities: true,
entities: ["dist/entities/*.entity.js"],
migrations: ["dist/migrations/*.js"],
cli: {
entitiesDir: "src/entities",
migrationsDir: "src/migrations"
}
}
・ex) 下記はCreateItemというマイグレーションファイル名で作成している例です。
npx typeorm migration:generate -n CreateItem
・ex) migrationを実行している例です。
npx typeorm migration:run
Repositoryの作成方法
・EntityRepositoryデコレータ をつけたクラスを定義する
・デコレータ の引数に Entity を渡す
・Repository<Entity> を継承する
import { Item } from 'src/entities/item.entity';
import { EntityRepository, Repository } from 'typeorm';
@EntityRepository(Item)
export class ItemRepository extends Repository<Item> {
async createItem(createitemDto: CreateItemDto): Promise<Item> {
const { name, price, description } = createitemDto;
// 親クラスの継承しているメソッドをthisでたどれる
const item = this.create({
name,
price,
description,
status: ItemStatus.ON_SALE,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
await this.save(item);
return item;
}
}
RepositoryをServiceから使えるようにする
・imports に TypeOrmmodule.forFeature([${Repository}]) を渡す
import { Module } from '@nestjs/common';
import { ItemsController } from './items.controller';
import { ItemsService } from './items.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ItemRepository } from './item.repository';
@Module({
imports: [TypeOrmModule.forFeature([ItemRepository])],
controllers: [ItemsController],
providers: [ItemsService],
})
export class ItemsModule {}
・Service で Repository を呼び出す
ex) constructor(private readonly itemRepository: ItemRepository) {} の部分です
@Injectable()
export class ItemsService {
constructor(private readonly itemRepository: ItemRepository) {}
async create(createItemDto: CreateItemDto): Promise<Item> {
return await this.itemRepository.createItem(createItemDto);
}
}
さいごに
学習させていただいたUdemy講座です。