web-dev-qa-db-ja.com

Nestコントローラーで404を返すデコレーター

私は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);
    }

これを行うデコレータ、または上記のものよりも優れたソリューションを知っている人はいますか?

6
Rich Duncan

これを行うための最短の方法は

@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;
}

それが誰かを助けることを願っています。

6
Valera

このための組み込みデコレータはありませんが、戻り値をチェックしてNotFoundExceptionundefinedをスローする 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);
}
3
Kim Kern

単純なケースの場合、私は通常、余分な綿毛を追加せずにこの怠惰な方法でそれを行います:

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
}
0
demisx

最新の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); }
      }));
  }
}


0
Maxime Lafarie