Reputation: 51
I am trying to build a NestJS backend with multiple microservices and one REST API as a Gateway which communicates with the microservices. For communication between the Gateway and Microservices I am using gRPC. Simple communication is already working, but now I wanted to implement Error Handling in the Microservices. The NestJS Documentation states that this is possible with the RpcException class.https://docs.nestjs.com/microservices/exception-filters But if I try to catch the Exception in the Gateway API I only get "ERROR [ExceptionsHandler] 2 UNKNOWN: ..." followed by the Exceptions error message.
Gateway API: user.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { UserViewModel } from '../../proto/build/service-name/user';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get(':id')
async getUserById(@Param('id') id: number): Promise<UserViewModel> {
try {
return await this.userService.getUserById(id);
} catch (error) {
return error;
}
}
}
Gateway API: user.service.ts
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import {
IUserService,
UserViewModel,
} from '../../proto/build/service-name/user';
@Injectable()
export class UserService implements OnModuleInit {
private grpcService: IUserService;
constructor(@Inject('USER_PACKAGE') private client: ClientGrpc) {}
onModuleInit() {
this.grpcService = this.client.getService<IUserService>('IUserService');
}
async getUserById(id: number): Promise<UserViewModel> {
return this.grpcService.getUserById({ id });
}
}
Microservice: user.controller.ts
import { Metadata } from '@grpc/grpc-js';
import { Controller } from '@nestjs/common';
import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { User } from './../../node_modules/.prisma/client/index.d';
import { PrismaService } from '../prisma/prisma.service';
import { UserViewModel, GetUserById } from '../proto/build/service-name/user';
@Controller()
export class UserController {
constructor(private readonly prisma: PrismaService) {}
@GrpcMethod('IUserService', 'getUserById')
async getUserById(
data: GetUserById,
metadata: Metadata,
): Promise<UserViewModel> {
const user: User = await this.prisma.user.findFirst({
where: { id: { equals: data.id } },
});
if (!user) {
throw new RpcException('User not found');
}
return { name: user.name, email: user.email };
}
}
Upvotes: 4
Views: 12732
Reputation: 1
i have tried on nestjs 9. just only
throw new RpcException({ code: grpc.status.UNAVAILABLE, message: 'error' })
ps: grpc need import like that
import * as grpc from '@grpc/grpc-js'.
Upvotes: 0
Reputation: 5813
I ran into this issue as well when building microservices with an API gateway. The solution I came up with is a combination of the answers I've found here but allows you to use the build in NestJS exceptions.
So basically I wrap the built in NestJS HTTP exception with the RpcException
in the microservice. Then you can catch the execption in your api gateway and handle it with a filter. The RcpException
message can be either a string
or object
, this allows you to pass the built in HTTP exceptions (NotFoundException
, UnauthorizedException
, etc) as a message so you don't have to deal with status codes.
// Some service method to fetch a product by id
public async findById(id: number): Promise<Product> {
// ...
if (!product) {
throw new RpcException(
new NotFoundException("Product was not found!");
);
}
//...
}
@Get(':productId')
public getProductById(@Param('productId') productId: number): Observable<any> {
return this._productsServiceClient
.send({ cmd: 'find-by-id' }, { productId })
.pipe(catchError(error => throwError(() => new RpcException(error.response))))
}
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { RpcException } from '@nestjs/microservices';
import { Response } from 'express';
@Catch(RpcException)
export class RpcExceptionFilter implements ExceptionFilter {
catch(exception: RpcException, host: ArgumentsHost) {
const error: any = exception.getError();
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
response
.status(error.statusCode)
.json(error);
}
}
// main.ts
app.useGlobalFilters(new RpcExceptionFilter());
Upvotes: 5
Reputation: 9
export interface IRpcException {
message: string;
status: number;
}
export class FitRpcException extends RpcException implements IRpcException {
constructor(message: string, statusCode: HttpStatus) {
super(message);
this.initStatusCode(statusCode);
}
public status: number;
private initStatusCode(statusCode) {
this.status = statusCode;
}
}
@Catch()
export class AllGlobalExceptionsFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: IRpcException, host: ArgumentsHost): void {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const httpStatus = exception.status
? exception.status
: HttpStatus.INTERNAL_SERVER_ERROR;
const responseBody = {
statusCode: httpStatus,
timestamp: new Date().toISOString(),
path: httpAdapter.getRequestUrl(ctx.getRequest()),
message: exception.message,
};
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}
}
@UseFilters(AllGlobalExceptionsFilter)
Upvotes: 0