I’m working on a backend using NestJS, (which is amazing btw). I have a ‘standard get a single instance of an entity situation’ similar to this example below.
@Controller('user') export class UserController { constructor(private readonly userService: UserService) {} .. .. .. @Get(':id') async findOneById(@Param() params): Promise<User> { return userService.findOneById(params.id); }
This is incredibly simple and works – however, if the user does not exist, the service returns undefined and the controller returns a 200 status code and an empty response.
In order to make the controller return a 404, I came up with the following:
@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(); } } .. ..
This works, but is a lot more code-y (yes it can be refactored).
This could really use a decorator to handle this situation:
@Get(':id') @OnUndefined(404) async findOneById(@Param() params): Promise<User> { return userService.findOneById(params.id); }
Anyone aware of a decorator that does this, or a better solution than the one above?
Advertisement
Answer
The shortest way to do this would be
@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; }
There is no point in decorator here because it would have the same code.
Note: BadRequestException
is imported from @nestjs/common
;
Edit
After some time with, I came with another solution, which is a decorator in the 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, }); }; }
Then in your DTO:
export class GetUserParams { @IsValidId('user', { message: 'Invalid User' }) id: number; }
Hope it helps someone.