Decorator to return a 404 in a Nest controller

There is no built-in decorator for this, but you can create an interceptor that checks the return value and throws a NotFoundException on undefined:

Interceptor

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

Then you can use the Interceptor by adding it to either a single endpoint:

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

or all endpoints of your Controller:

@Controller('user')
@UseInterceptors(NotFoundInterceptor)
export class UserController {

Dynamic Interceptor

You can also pass values to your interceptor to customize its behavior per endpoint.

Pass the parameters in the constructor:

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

and then create the interceptor with new:

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

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.