Open8
NestJSのTips
リクエストとかレスポンスをどうするか
- リクエストはDtoを作って定義する
- ここにclass validatorもつける
- class validatorはnestもできるので、入れ子のdictの中もチェックしたいときは@ValidateNested()をつけるとよい
- レスポンス
- このとき
IntersectionType
などを使って既存のEntityや型を組み合わせたりすると楽
- このとき
このへんの詳しい書き方はあとで記事をかく。
JWTまわり
チュートリアルにもあるJWTの署名は秘密鍵がローカルにある必要がある。普通KMSとかで鍵自体はおかないようにすることが多いので、チュートリアルのようにPassportを使ってJWTを実装するときは気を付ける必要がある。
とりあえずプログラマの目にとまるよってだけなのでSecret Managerあたりで鍵を隠す+鍵のローテーションをするという運用がよさそう。
テスト
AuthGuardを使ったとき、access_tokenなんていちいちテスト中にわからないので、モックする。
stackoverflowを参考に、
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
.overrideGuard(Guard1)
.useValue({ canActivate: () => true })
.overrideGuard(Guard2)
.useValue({ canActivate: () => true })
.compile();
app = moduleFixture.createNestApplication();
await app.init();
});
こんな感じでoverrideGuard
してuseValue
で値を指定すればOK。ちなみにNestJSにはbeforeAllかbegoreEachにmoduleに書いた依存関係を書く落とし穴があります。
bcryptでパスワードを守る
- saltRoundsは大きすぎず小さすぎない値である必要がある。リポジトリのサンプルの10が無難そう
- bcryptはbcyptのリポジトリにもあるようにasyncでハッシュにすることを推奨している
//hash化するとき
const password_hash = await bcrypt.hash(password, 10);//10はsaltRounds
//compareするとき
const result = await bcrypt.compare(password, password_hash);
import
- なぜか相対パスでやらないとエラーになることがある
テストのモック
- Unit TestだとspyOnでモックできる
- e2eテストの場合、createTestingModuleを使うはず。なので依存するserviceについてprovideとuserValueを使ってモックする
- インスタンス化をテストコード内でしていないとspyOnが使えないので
- 当たり前だけどe2eテストはHTTP通信をしたことにするので、まああんまりメソッドのモックとかはしないんだろうなという気持ち
NestJSの入出力のバリデーションとかにおすすめのライブラリ
Interceptorの使い所
validation pipeはclass-validatorに使うのかなとすぐ理解できたのですが、intercepterだけ使い所がわからなかったのでメモ。
class transformerとかを使うときに使う。
例えば
- あるjsonの中で、名前つけかえたい
- passwordだけ抜きたい
みたいなのをアノテーションひとつでできる感じです。
使い方
同じような有名どころライブラリにclass validatorがあるのですが、これとほぼ導入方法は同じです。main.tsに以下をかきます。
//class-validatorを使うときに
nestApp.useGlobalPipes(new ValidationPipe());
//class-transformerを使うときに
nestApp.useGlobalInterceptors(
new ClassSerializerInterceptor(nestApp.get(Reflector)),
);
あとはリポジトリにある例を見てentityなりDTOなりのクラスにアノテーションをつけるだけ。
この2つのライブラリはNestJS使うときは入れておくとよさそう。
他のServiceを使う時のチェックポイント
- moduleのimportに相手のmoduleをimportしているか
- importしたうえでprovidersに使う相手のserviceを書いているか
- 自分のserviceのconstructorに相手のserviceを書いているか
- testをしている場合、testのmoduleにも上記を反映しているか
わりとこのへんつまってdependenciesエラーが出て死ぬのでメモ
Nestでつくるときのメモ
controller
DTO
- req / resの値を入れておく
import {ApiProperty} from '@nestjs/swagger';
import {IsString* from 'class-validator';
import {HogeModel} from './hoge.model';
export class GetHogeRequestDto{
@ApiProperty({ type: String, description: 'test', example: 'てすとです'})
@IsString()
test: string;
}
// Modelに一部追加してレスポンスを返す場合の例
export class GetHogeResponseDto extends HogeModel{
// こっちはクライアントからのリクエストではないのでclass-validatorはいらない
@ApiProperty({ type: [String], description: 'テスト一覧', example: ['テスト1', 'テスト2']})
test_list: string[]:
}
controller
import {Body, Controller, HttpCode, HttpException, Post, UseGuards}
import {ApiBody, ApiOperation, ApiResponse, ApiTags} from '@nestjs/swagger';
import {JwtAuthGuard} from '../auth/hoge.jwt.stragegy'; //jwtとかのstrategyをt雨くる
import {HogeModel} from '../models/hoge.model';
import {HugaService} from './huga.service';
import * as HogeDTO from './dto/hoge.dto';
@Controller('huga')
@ApiTags('huga')
export class HugaController{
constructor(private readonly hugaService: HugaService){}
@Post('/')
@HttpCode(200)
@ApiOperation({summary: 'hogeを取得'})
@ApiResponse({type: HogeDTO.GetHogeResponseDto, status: 200, description: 'hoge'})
@ApiBody({type: HogeDTO.GetHogeRequestDto})
@UseGuards(JwtAuthGuard)
async get(@Body() body: HogeDTO.GetHogeRequestDto): Promise<HogeDTO.GetHogeResponseDto>{
const hoge = await this.hugaService.get();
return {
...service,
test_list: ['test1']
}
}
}
module
import {Module} from '@nestjs/common';
import {HugaService} from './huga.service';
import {HugaController} from './huga.controller';
@Module({
providers: [HugaService],
controllers: [HugaController],
})
export class HugaModule {}
service
import {HttpExeption, HttpStatus, Injectable} from '@nestks/common';
import {HogeModel, HogeStore} from './models/hoge.model';
@injectable()
export class HugaService{
async get(); Promise<>{
const hoge = await hogeStore.scan().execFetchAll();
if(hoge?.length === 0){
throw new HttpException("hoge is not exists", HttpStatus.NOT_FOUND);
}
return hoge;
}
}
DB接続まわり
- DynamoDBだったのでDBとの接続クラスを作る(dynamo-easyなどのラッパーを使用)
- modelがテーブルごとのクラスで、一応全部をまとめるbasemodelを空のクラスでつくっておく(あとで型定義に使うかも)
- swaggerの定義をかくとDTOを作るときに楽(だけど、DTOで受け取る値を絞るような運用のときはやらないほうがいい。例えばpasswordなどの値を入れたくないとか)
hoge.model.ts
import {ApiProperty} from '@nestjs/swagger';
import {DynamoStore, Model, PartitionKey} from '@shiftcoders/dynamo-easy';
import {BaseModel} from './base.model';
@Mode({tableName: `Hoge_${process.env.STAGE}`}) //複数ステージで運用する
export class HogeModel extends BaseModel{
@PartitionKey()
@ApiProperty({ type: String, description: 'hogeid', example: 'id1'})
id: string;
@ApiProperty({ type: String, description: 'name', example: 'なまえ'})
name: string;
}
export const HogeStore = new DynamoStore(HogeModel);
あとでかく
- enablecors
- env使う(configmodule)
- globalpipes
- serverlessで動かす