Decorator在Nest控制器中返回404

Ric*_*can 5 javascript node.js nestjs

我正在使用NestJS开发后端(这真是太棒了).我有一个'标准获取实体情况的单一实例',类似于下面这个例子.

@Controller('user')
export class UserController {
    constructor(private readonly userService: UserService) {}
    ..
    ..
    ..
    @Get(':id')
    async findOneById(@Param() params): Promise<User> {
        return userService.findOneById(params.id);
    }
Run Code Online (Sandbox Code Playgroud)

这非常简单且有效 - 但是,如果用户不存在,则服务返回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();
        }
    }
    ..
    ..
Run Code Online (Sandbox Code Playgroud)

这是有效的,但代码更多(是的,它可以重构).

这可能真的使用装饰器来处理这种情况:

    @Get(':id')
    @OnUndefined(404)
    async findOneById(@Param() params): Promise<User> {
        return userService.findOneById(params.id);
    }
Run Code Online (Sandbox Code Playgroud)

任何人都知道这样做的装饰者,或者比上面的解决方案更好的解决方案?

Kim*_*ern 8

没有为此内置装饰器,但您可以创建一个拦截器来检查返回值并抛出一个NotFoundExceptionon undefined

拦截器

@Injectable()
export class NotFoundInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle()
      .pipe(tap(data => {
        if (data === undefined) throw new NotFoundException();
      }));
  }
}
Run Code Online (Sandbox Code Playgroud)

然后您可以Interceptor通过将其添加到单个端点来使用:

@Get(':id')
@UseInterceptors(NotFoundInterceptor)
findUserById(@Param() params): Promise<User> {
    return this.userService.findOneById(params.id);
}
Run Code Online (Sandbox Code Playgroud)

或您的所有端点Controller

@Controller('user')
@UseInterceptors(NotFoundInterceptor)
export class UserController {
Run Code Online (Sandbox Code Playgroud)

动态拦截器

您还可以将值传递给拦截器以自定义其每个端点的行为。

在构造函数中传递参数:

@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);
                                                            ^^^^^^^^^^^^^^^^^
      }));
  }
}
Run Code Online (Sandbox Code Playgroud)

然后使用以下命令创建拦截器new

@Get(':id')
@UseInterceptors(new NotFoundInterceptor('No user found for given userId'))
findUserById(@Param() params): Promise<User> {
    return this.userService.findOneById(params.id);
}
Run Code Online (Sandbox Code Playgroud)

  • @teteArg 这是不正确的。HTTP 规范非常广泛,没有定义资源是什么。当您调用“/users/49”并且不存在请求的 id 为 49 的用户(资源)时,适合响应 404 - 未找到资源。但是,这取决于您的 API 设计,空的 200 响应也可能有意义。另请参阅此线程:/sf/answers/696256431/ (3认同)
  • 我相信这(以及下面马克西姆的补充)是对已接受答案的更优雅的解决方案 (2认同)

Max*_*rie 8

@Kim Kern对最新 Nestjs 版本的回答的更新版本:

正如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); }
      }));
  }
}


Run Code Online (Sandbox Code Playgroud)


Val*_*era 6

最简单的方法是

@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;
}
Run Code Online (Sandbox Code Playgroud)

这里的装饰器没有意义,因为它将具有相同的代码。

注: 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,
        });
    };
}

Run Code Online (Sandbox Code Playgroud)

然后在您的DTO中:

export class GetUserParams {
    @IsValidId('user', { message: 'Invalid User' })
    id: number;
}
Run Code Online (Sandbox Code Playgroud)

希望它可以帮助某人。

  • 当这显然是“NotFoundException”时,为什么每个人都抛出“BadRequestException”?错误请求是针对格式错误的请求对象的。如果您需要检查数据库,这不是一个糟糕的请求问题。 (8认同)
  • 您也可以只抛出`BadRequestException`。 (2认同)

dem*_*isx 5

如果这是一个简单的情况,我通常会用这种懒惰的方式来做,而不添加额外的绒毛:

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
}
Run Code Online (Sandbox Code Playgroud)