Nest.js
モチベーション
- Nest.jsの概要を知る
- Nest.jsの環境構築 + HelloWorld
- 基本文法を知る
- フロントエンド側との疎通
- GraphQLとの疎通
- gRPCサーバとの疎通
参考
Nest.jsとは
- Node.jsで作成されているバックエンド開発フレームワーク
- TypeScriptで作られている
- コアがExpress
- Angularエクスパイア
Nest.jsのメリット
- 型の恩恵
- Expressの機能、ライブラリが利用可
- NestCLIでプロジェクト、ファイルのテンプレートを生成可
- テストフレームワークが標準装備
- 拡張性が高い
Nest.jsのデメリット
- 既存バックエンドフレームワークと比べると情報が少ない
NestCLI
以下のコマンドを利用可能
nest new プロジェクト名
nest g controller コントローラ名
Nest CLIのプロジェクトについて
- src
- spec
- テストコード
- controller
- module
- service
- main.ts
- プロジェクトのエントリーポイント
- spec
- test
- テストを格納するディレクトリ
Nest.jsの基本アーキテクチャ
コアな要素
- Controller
- Service
- Module
main.ts
↓
app.module.ts
- ルートモジュール
- 開発機能を集約し、アプリケーションに登録する役割
↓
feature.module.ts
→ feature.service.ts
→ feature.controller.ts
Module
- 関連するController, Serviceをまとめアプリケーションとして利用できるようにする役割
- Nest.jsには必ず1つ以上のルートモジュールが必要
モジュール定義
classに@Moduleデコレータをつける
@Module({
...
})
export class AppModule()
@Moduleデコレーターのプロパティ
- providers
- @Injectable デコレーターがついたクラスを記述
- controllers
- @Controllerデコレーターがついたクラスを記述
- imports
- モジュール内部で必要な外部モジュールを記述
- exports
- 外部のモジュールにエクスポートしたいものを記述
サンプルコード
app.module.ts
@Module({
imports: [UserModule],
controllers: [AppController],
providers: [AppService],
exports: []
})
export class AppModule {}
users.module.ts
@Module({
imports: [],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService]
})
export class UsersModule {}
Nest.jsのModule利用方法
- 手動でファイル作成し、一から書いていく
- Nest CLIを使ってモジュールの雛形を作り、必要部分だけを記載
ターミナルコマンド
nest g module モジュール名
モジュールが作成される
import { Module } from '@nestjs/common';
import { ItemsController } from './items.controller';
@Module({
controllers: [ItemsController],
})
export class ItemsModule {}
自動でapp.modules.tsにも登録される
import { Module } from '@nestjs/common';
import { ItemsModule } from './items/items.module';
@Module({
imports: [ItemsModule],
controllers: [],
providers: [],
})
export class AppModule {}
Controller
- ルーティング機能
- クライアントからリクエストを受け、レスポンスを返す
- 特定のパスとコントローラが紐づく(/usersとUsersController)
Controllerの定義
- @Controller()デコレータをつける
- パスと紐づく
メソッドの定義
メソッドにHTTPメソッドデコレーターをつける
@Controller('users')
export class UsersController {
@Post()
create() {
// Create User
}
}
コマンド(--no-specをつけるとテストファイルなし)
nest g controller コントローラー名 --no-spec
items.controller.tsが作成される
import { Controller } from '@nestjs/common';
@Controller('items')
export class ItemsController {}
controllerを記述
import { Controller, Get } from '@nestjs/common';
@Controller('items')
export class ItemsController {
@Get()
findAll() {
return 'this is findall';
}
}
nest.jsの場合、ローカルサーバ立ち上げは npm run start:dev
でok
APIの確認は、PostManで確認
上記のコントローラーの場合、以下のurlでok
http://localhost:3000/items
Service
- ビジネスロジックの定義
- Controllerから呼び出す
Controllerはルーティング(urlとビジネスロジックの紐づけ)
Serviceはビジネスロジック
として分離する
DI
依存性の注入
Controller → Serviceの依存関係を作る
Controller外部でサービスインスタンスを作成し、Controllerに渡す方法が一般的
外部からインスタンスを渡す作りにすることで、利用するインスタンスを切り替えたいときなどに内部のコードを書き換えずにできる。
- 本番用とテスト用のインスタンス切り替え
- ログ出力先の切り替え
などが容易になる
Nest.jsなどのフレームワークにはDIしやすい仕様が整えられていることが多い
Serviceの定義
import { Injectable } from '@nestjs/common'
@Injectable()
export class UsersService {
find(userName: number) {
// findUser
}
}
作成したサービスをコントローラで使うには
- DIの為の設定(モジュールのprovidersに記述)
- コントローラ側のconstructorでサービスを引数に取る
NestCLIによるサービスの作成
nest g service items --no-spec
module
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
controller
@Controller('users')
export class UsersController {
constructor(private readonly userService: UsersService) {}
@Get(':username')
find(@Param('username') userName: string) {
this.userService.find(userName)
}
}
Nest.jsでCRUD実装(create)
@Bodyデコレータを用いることで、リクエストbodyで受け取る値を引数として定義できる
↓コントローラ
@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);
}
↓サービス
import { Injectable } from '@nestjs/common';
import { Item } from './item.model';
@Injectable()
export class ItemsService {
private items: Item[] = [];
findAll(): Item[] {
return this.items;
}
create(item: Item): Item {
this.items.push(item);
return item;
}
}
update, delete
↓service
updateStatus(id: string): Item {
const item = this.findById(id);
item.status = ItemStatus.SOLD_OUT;
return item;
}
delete(id: string): void {
this.items = this.items.filter((item) => item.id !== id);
}
↓controller(Patch, Deleteのそれぞれのデコレータを利用。URLパラメータは@Paramデコレータを利用)
@Patch(':id')
updateStatus(@Param('id') id: string): Item {
return this.itemsService.updateStatus(id);
}
@Delete(':id')
delete(@Param('id') id: string): void {
return this.itemsService.delete(id);
}
DTO(Data Transfer Object)
- データ受け渡しに使われるオブジェクト
- DBとモデルクラス感のデータのやり取り
- リクエストオブジェクトからのデータ受けとり
メンテナンス性、安全性が高まる
Nest.jsのバリデーションを利用可能
DTOクラス作成
export class CreateItemDto {
id: string;
name: string;
price: number;
description: string;
}
controllerのBodyデコレータをDTOで置き換えることができる
@Post()
create(
@Body('id') id: string,
@Body('name') name: string,
@Body('price') price: number,
@Body('description') description: string,
)
↓
@Post()
create(@Body() createItemDto: CreateItemDto)
※Bodyデコレータに渡すパラメータは空でok
※classバリデータを利用するため、DTOがinterfaceやtypeではなくclassで定義する
バリデーション
リクエストオブジェクトの形式チェック
Nest.jsではPipeという機能を利用する
ハンドラーがリクエストを受け取る前にリクエストに対して処理
データの変換、バリデーションが可能
処理を行ったあとのデータをハンドラーに返す
Pipe処理内で例外を返すこともできる
Pipeのメソッド群
- ValidationPipe
- ParseIntPipe
- ParseBoolPipe
- ParseUUIDPipe
- DefaultValuePipe
Pipeの利用方法
- @UsePipesデコレータを利用し、上記のメソッドを定義
- リクエストパラメータ毎に適用することも可能
- main.tsでPipeを利用することでグローバルなバリデーションも可能
リクエストパラメータに対して、UUID形式が利用されているかを確認する場合
以下の様に@Paramデコレータの第二引数にParseUUIDPipeを記述
@Get(':id')
findById(@Param('id', ParseUUIDPipe) id: string): Item {
return this.itemsService.findById(id);
}
Postmanでアクセスすると
localhost:3000/items/test2
↓こういったエラー
{
"message": "Validation failed (uuid is expected)",
"error": "Bad Request",
"statusCode": 400
}
uuid形式だとエラーなしで取得可
localhost:3000/items/3d60e23b-18b8-4cd5-b511-404ac8c2b99e
Class Validatorについて
npm i class-validator class-transformer
DTOに対して以下のような記述を行うことで各プロパティに対するバリデーションを実装することができる
import { Type } from 'class-transformer';
import { IsString, IsNotEmpty, MaxLength, IsInt, Min } from 'class-validator';
export class CreateItemDto {
@IsString()
@IsNotEmpty()
@MaxLength(40)
name: string;
@IsInt()
@Min(1)
@Type(() => Number)
price: number;
@IsString()
@IsNotEmpty()
description: string;
}
main.tsに以下を記述
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common'; // 追加
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe()); // 追加
await app.listen(3000);
}
bootstrap();
Postmanで、バリデーションルールに引っかかる状態でPOSTを実行したとき以下のメッセージが表示される
{
"message": [
"name should not be empty"
],
"error": "Bad Request",
"statusCode": 400
}
例外処理
デフォルトで定義されている例外処理メソッドを利用することも可能
import { NotFoundException } from '@nestjs/common';
findById(id: string): Item {
const found = this.items.find((item) => item.id === id);
if (!found) {
throw new NotFoundException();
}
return found;
}
NestJSのORMライブラリ
- TypeORM
※ Prismaもあり
Entity
- RDBのテーブルと対応するオブジェクト
- @Entityデコレータをつけたクラスとして定義
- @PrimaryGeneratedColumnデコレータや@ColumnデコレーターがついたプロパティがRDBのColumnとマッピングされる
Repository
- Entityを管理する為のオブジェクト
- EntityとRepositoryが1:1となりDB操作を抽象化する
- クラスに@EntityRepository()デコレータをつけて、Repositoryを継承する