私はNestJSを使用してバックエンドに取り組んでいます(これは驚くべきことです)。以下の例のように、「エンティティシチュエーションの単一インスタンスを取得する標準」があります。
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
..
..
..
@Get(':id')
async findOneById(@Param() params): Promise<User> {
return userService.findOneById(params.id);
}
これは非常にシンプルで機能しますが、ユーザーが存在しない場合、サービスはundefinedを返し、コントローラーは200ステータスコードと空の応答を返します。
コントローラに404を返すようにするために、私は次のことを思いつきました。
@Get(':id')
async findOneById(@Res() res, @Param() params): Promise<User> {
const user: User = await this.userService.findOneById(params.id);
if (user === undefined) {
res.status(HttpStatus.NOT_FOUND).send();
}
else {
res.status(HttpStatus.OK).json(user).send();
}
}
..
..
これは機能しますが、コードがはるかに多くなります(はい、リファクタリングできます)。
これは実際にデコレータを使用してこの状況を処理できます。
@Get(':id')
@OnUndefined(404)
async findOneById(@Param() params): Promise<User> {
return userService.findOneById(params.id);
}
これを行うデコレータ、または上記のものよりも優れたソリューションを知っている人はいますか?
これを行うための最短の方法は
@Get(':id')
async findOneById(@Param() params): Promise<User> {
const user: User = await this.userService.findOneById(params.id);
if (user === undefined) {
throw new BadRequestException('Invalid user');
}
return user;
}
同じコードになるため、ここではデコレータに意味がありません。
注:BadRequestException
は@nestjs/common
からインポートされます。
編集
しばらくして、DTOのデコレータである別のソリューションを思いつきました。
import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint } from 'class-validator';
import { createQueryBuilder } from 'typeorm';
@ValidatorConstraint({ async: true })
export class IsValidIdConstraint {
validate(id: number, args: ValidationArguments) {
const tableName = args.constraints[0];
return createQueryBuilder(tableName)
.where({ id })
.getOne()
.then(record => {
return record ? true : false;
});
}
}
export function IsValidId(tableName: string, validationOptions?: ValidationOptions) {
return (object, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [tableName],
validator: IsValidIdConstraint,
});
};
}
次に、DTOで:
export class GetUserParams {
@IsValidId('user', { message: 'Invalid User' })
id: number;
}
それが誰かを助けることを願っています。
このための組み込みデコレータはありませんが、戻り値をチェックしてNotFoundException
にundefined
をスローする interceptor を作成できます。
@Injectable()
export class NotFoundInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, stream$: Observable<any>): Observable<any> {
// stream$ is an Observable of the controller's result value
return stream$
.pipe(tap(data => {
if (data === undefined) throw new NotFoundException();
}));
}
}
次に、Interceptor
を単一のエンドポイントに追加して使用できます。
@Get(':id')
@UseInterceptors(NotFoundInterceptor)
findUserById(@Param() params): Promise<User> {
return this.userService.findOneById(params.id);
}
またはController
のすべてのエンドポイント:
@Controller('user')
@UseInterceptors(NotFoundInterceptor)
export class UserController {
インターセプターに値を渡して、エンドポイントごとの動作をカスタマイズすることもできます。
コンストラクターでパラメーターを渡します。
@Injectable()
export class NotFoundInterceptor implements NestInterceptor {
constructor(private errorMessage: string) {}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
intercept(context: ExecutionContext, stream$: Observable<any>): Observable<any> {
return stream$
.pipe(tap(data => {
if (data === undefined) throw new NotFoundException(this.errorMessage);
^^^^^^^^^^^^^^^^^
}));
}
}
次に、new
を使用してインターセプターを作成します。
@Get(':id')
@UseInterceptors(new NotFoundInterceptor('No user found for given userId'))
findUserById(@Param() params): Promise<User> {
return this.userService.findOneById(params.id);
}
単純なケースの場合、私は通常、余分な綿毛を追加せずにこの怠惰な方法でそれを行います:
import {NotFoundException} from '@nestjs/common'
...
@Get(':id')
async findOneById(@Param() params): Promise<User> {
const user: User = await this.userService.findOneById(params.id)
if (!user) throw new NotFoundException('User Not Found')
return user
}
最新のNestjsバージョンの @ Kim Kernの回答 の更新バージョン:
言ったように Nestjsドキュメントで :
インターセプターAPIも簡素化されました。さらに、コミュニティによって報告されたこの 問題 のために変更が必要でした。
更新されたコード:
import { Injectable, NestInterceptor, ExecutionContext, NotFoundException, CallHandler } from '@nestjs/common';
import { Observable, pipe } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class NotFoundInterceptor implements NestInterceptor {
constructor(private errorMessage: string) { }
intercept(context: ExecutionContext, stream$: CallHandler): Observable<any> {
return stream$
.handle()
.pipe(tap(data => {
if (data === undefined) { throw new NotFoundException(this.errorMessage); }
}));
}
}