I am using nestjs.
I have created an AllExceptionFilter.
However, once I run the post request api on the httpService and then an error is returned
nestjs will not accept the RESTful API after that.
What should I do?
■ error log
/Users/username/Documents/workspace/project/nestjs/src/shared/filters/custom-exception.filter.ts:29
path: httpAdapter.getRequestUrl(ctx.getRequest()),
^
TypeError: Cannot read properties of undefined (reading 'getRequestUrl')
at AllExceptionsFilter.catch (/Users/username/Documents/workspace/project/nestjs/src/shared/filters/custom-exception.filter.ts:29:25)
at ExceptionsHandler.invokeCustomFilters (/Users/username/Documents/workspace/project/nestjs/node_modules/#nestjs/core/exceptions/exceptions-handler.js:33:26)
at ExceptionsHandler.next (/Users/username/Documents/workspace/project/nestjs/node_modules/#nestjs/core/exceptions/exceptions-handler.js:13:18)
at /Users/username/Documents/workspace/project/nestjs/node_modules/#nestjs/core/router/router-proxy.js:13:35
at processTicksAndRejections (node:internal/process/task_queues:96:5)
■ no try/catch point code
await firstValueFrom(this.httpService.post(
url,
{
id: 'id'
},
));
■ AllExceptionFilter
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '#nestjs/common';
import { HttpAdapterHost } from '#nestjs/core';
#Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: unknown, host: ArgumentsHost): void {
// In certain situations `httpAdapter` might not be available in the
// constructor method, thus we should resolve it here.
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const httpStatus =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const responseBody = {
statusCode: httpStatus,
timestamp: new Date().toISOString(),
path: httpAdapter.getRequestUrl(ctx.getRequest()),
};
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}
}
Looks like an error is thrown by your endpoint, and the exception filter has an error that is triggered when attempting to handle the first exception.
As specified in the stack trace, the error is coming from: custom-exception.filter.ts:29, which is this line: path: httpAdapter.getRequestUrl(ctx.getRequest())
The problem is that httpAdapter in that line is undefined. For some reason DI isn't injecting it.
If you're using this filter globally, note the following from the docs:
Global-scoped filters are used across the whole application, for every controller and every route handler. In terms of dependency injection, global filters registered from outside of any module (with useGlobalFilters() as in the example above) cannot inject dependencies since this is done outside the context of any module. In order to solve this issue, you can register a global-scoped filter directly from any module using the following construction:
import { Module } from '#nestjs/common';
import { APP_FILTER } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
If you use the above approach you won't need the useGlobalFilters() method.
If you want to bind this filter at the controller level, the following syntax enables DI:
#UseFilters(AllExceptionsFilter)
#Controller("app")
export class AppController {
Related
I replaced the nestjs logger with a custom for having it also for bootstrap messages.
This is the code i have used to instantiate it
const logger = CustomLoggerModule.createLogger();
try {
const app = await NestFactory.create(AppModule, {
logger,
});
} catch (e) {
logger.error(e)
}
createLogger is a static method of the CustomLoggerModule class that returns an instance of the CustomLoggerModule class, a class that implements Nest's LoggerService interface.
I now want to have access to the Logger inside the AppModule and this is what I've tried:
#Module({
imports: [
ConfigModule.forRoot(),
ESModule,
ElasticSearchConfigModule,
KafkaConfigModule,
IndexModule,
AppConfigModule,
ReadyModule,
],
providers: [
{
provide: APP_FILTER, //you have to use this custom provider
useClass: ErrorFilter, //this is your custom exception filter
},
Logger,
],
})
export class AppModule implements NestModule {
constructor(private moduleRef: ModuleRef) {}
configure(consumer: MiddlewareConsumer) {
const logger = this.moduleRef.get(Logger) as CustomLoggerModule;
const logger.initMiddleware(consumer);
}
}
But in this way, I get an exception at the line logger.initMiddleware, because the logger returned by moduleRef is of type Logger
Obviously by trying to access CustomLogger inside modules other than the AppModule by dependency injection works as expected
How can i access my custom logger instance inside the appModule ?
From Nest: "Because application instantiation (NestFactory.create()) happens outside the context of any module, it doesn't participate in the normal Dependency Injection phase of initialization."
So, you must ensure that at least one application module imports a CustomloggerModule to trigger Nest to instantiate a singleton instance of the custom logger class.
One of your application modules needs to have the following:
providers: [Logger]
exports: [Logger]
Also, your not setting the value of the optional logger property in NestFactory.create() to an object that fulfills the LoggerService interface. Try:
{
logger: logger
}
Then, you need to instruct Nest to use the logger singleton instance in your bootstrap:
const app = await NestFactory.create(AppModule, {
logger,
});
app.useLogger(logger);
So I'm building an httpgateway which sends messages to a microservice made with nestjs/grpc.
Problem is that, once I decorate my controller with #UsePipes(....) it throws an error for the gateway. I tried to log data entering into pipe and found out that grpc sends not only payload but also metadata and ServerDuplexStream prior to payload itself. So, my consumer throws an error because it faces with the ServerDuplexStream at first and cannot validate the args inside it.
I further tried to use my pipes in app.service but it doesnt make any sense since pipes receive data from the request. So it doesnt work as expected.
Is there a workaround like putting all three in a call in my gateway prior to sending request?
You can see an example of a pipe that im trying to implement:
#Injectable()
export class ValidateSingleBalanceByUser implements PipeTransform {
transform(value: SingleBalanceDto) {
if (!value.user) throw new RpcException('Provide user value to query!');
if (!value.asset) throw new RpcException('Provide asset value to query!');
return value;
}
}
and an example of a controller that im trying to implement to
#UsePipes(new ValidateSingleBalanceByUser())
#GrpcMethod('BridgeService', 'getSingleBalanceByUser')
singleBalanceByUser(data: SingleBalanceDto): Promise<Balance> {
return this.balancesService.handleSingleBalanceByUser(data);
}
I was getting the same problem, cause the Validation its a pipe and does not treat grpc exceptions. I fixed it using this solution:
controller.ts
#GrpcMethod('service','action')
#UsePipes(new ValidationPipe({ transform: true }))
#UseFilters(new TranslateHttpToGrpcExceptionFilter())
method(){}
translate.ts
#Catch(HttpException)
export class TranslateHttpToGrpcExceptionFilter implements ExceptionFilter {
static HttpStatusCode: Record<number, number> = {
[HttpStatus.BAD_REQUEST]: status.INVALID_ARGUMENT,
[HttpStatus.UNAUTHORIZED]: status.UNAUTHENTICATED,
[HttpStatus.FORBIDDEN]: status.PERMISSION_DENIED,
[HttpStatus.NOT_FOUND]: status.NOT_FOUND,
[HttpStatus.CONFLICT]: status.ALREADY_EXISTS,
[HttpStatus.GONE]: status.ABORTED,
[HttpStatus.TOO_MANY_REQUESTS]: status.RESOURCE_EXHAUSTED,
499: status.CANCELLED,
[HttpStatus.INTERNAL_SERVER_ERROR]: status.INTERNAL,
[HttpStatus.NOT_IMPLEMENTED]: status.UNIMPLEMENTED,
[HttpStatus.BAD_GATEWAY]: status.UNKNOWN,
[HttpStatus.SERVICE_UNAVAILABLE]: status.UNAVAILABLE,
[HttpStatus.GATEWAY_TIMEOUT]: status.DEADLINE_EXCEEDED,
[HttpStatus.HTTP_VERSION_NOT_SUPPORTED]: status.UNAVAILABLE,
[HttpStatus.PAYLOAD_TOO_LARGE]: status.OUT_OF_RANGE,
[HttpStatus.UNSUPPORTED_MEDIA_TYPE]: status.CANCELLED,
[HttpStatus.UNPROCESSABLE_ENTITY]: status.CANCELLED,
[HttpStatus.I_AM_A_TEAPOT]: status.UNKNOWN,
[HttpStatus.METHOD_NOT_ALLOWED]: status.CANCELLED,
[HttpStatus.PRECONDITION_FAILED]: status.FAILED_PRECONDITION
}
catch(exception: HttpException): Observable<never> | void {
const httpStatus = exception.getStatus()
const httpRes = exception.getResponse() as { details?: unknown, message: unknown }
return throwError(() => ({
code: TranslateHttpToGrpcExceptionFilter.HttpStatusCode[httpStatus] ?? status.UNKNOWN,
message: httpRes.message || exception.message,
details: Array.isArray(httpRes.details) ? httpRes.details : httpRes.message
}))
}
}
Hope it helps!
I'm trying to write a test that checks if request body does not have proper data, it should return an error, and the status code should be 400. Which I think is by default for nestjs's validator.
This is my contorller,
#Post("register/client-business-manager")
async registerClientBusinessManager(#Body() form: RegisterClientBusinessManager): Promise<any> {
return { something: "ok" }
}
This is my form request class,
export class RegisterClientBusinessManager {
#IsNotEmpty()
#MaxLength(100)
#IsString()
first_name: string
#IsNotEmpty()
#MaxLength(100)
#IsString()
last_name: string
// ... other fields
}
it("returns 400 if proper data is not given", () => {
return request(app.getHttpServer())
.post("/auth/register/client-business-manager")
.expect(HttpStatus.BAD_REQUEST)
})
But the response I get,
expected 400 "Bad Request", got 201 "Created"
38 | return request(app.getHttpServer())
39 | .post("/auth/register/client-business-manager")
> 40 | .expect(HttpStatus.BAD_REQUEST)
| ^
41 | })
42 | })
43 |
I'm also using a global validation pipe,
In my main.ts,
const app = await NestFactory.create(AppModule)
app.setGlobalPrefix("api/v1")
app.useGlobalPipes(new ValidationPipe(VALIDATION_PIPE_OPTIONS))
On the other hand, making a request from postman gives the proper 400 bad request.
I'm new to nestjs. Why might be the reason for this type of behaviour and how can I solve it?
Okay, so you've bound the ValidationPipe globally in the main.ts file's bootstrap method. You don't call that method in your test though, so the pipe never gets bound and you need to still call app.useGlobalPipes(new ValidationPipe()) in the test file. Another option would be to use the APP_PIPE global provider and bind the pipe globally that way. In your AppModule (or any other module honestly) you can do the following:
#Module({
imports: ServerImports,
providers: [...ServerProviders,
{
provide: APP_PIPE,
useValue: new ValidationPipe(validationPipeOptions),
}
],
controllers: ServerControllers,
})
export class AppModule {}
Now whenever you use the AppModule, whether it is in a test or in the main.ts you have the pipe globally bound and you don't need to worry about binding it again.
Looks like you are not using the Validation Pipe from NestJs. You can either:
Register a global Validation Pipe in your bootstrap function like this:
async function bootstrap() {
// ...
app.useGlobalPipes(new ValidationPipe());
// app.listen(), etc.
}
or you can register the ValidationPipe, in using your #Body decorator like this:
#Post("register/client-business-manager")
async registerClientBusinessManager(#Body(ValidationPipe) form: RegisterClientBusinessManager): Promise<any> {
return { something: "ok" }
}
EDIT: You can also register a pipe at controller level using the #UsePipes decorator
I've found my mistake.
I was mistakenly registering the validation pipe after app.init().
So the solution was this,
app = moduleFixture.createNestApplication()
app.useGlobalPipes(new ValidationPipe(VALIDATION_PIPE_OPTIONS))
await app.init()
instead of
await app.init()
app.useGlobalPipes(new ValidationPipe(VALIDATION_PIPE_OPTIONS))
i'm new in NestJS and have some misunderstands with #liaoliaots/nestjs-redis(https://github.com/liaoliaots/nestjs-redis) package. For example, i have a guard with following constructor:
import { InjectRedis } from '#liaoliaots/nestjs-redis';
import { Redis } from 'ioredis';
#Injectable()
export class SomeGuard implements CanActivate {
constructor(#InjectRedis() redis: Redis) {}
...
}
and then i want that guard to be global:
//main.ts
...
app.useGlobalGuards(new SomeGuard(/* what??? */));
...
so thats a problem: what i need to pass? #InjectRedis makes weird things:)
thx for responding
Instead of app.useGlobalGuards, use this another way:
// ...
import { Module } from '#nestjs/common'
import { APP_GUARD } from '#nestjs/core'
#Module({
// ...
providers: [
{
provide: APP_GUARD,
useClass: SomeGuard,
},
],
})
export class AppModule {}
is cleaner and helps you avoid polluting your boostrap funcion. Also, it lets Nest resolves that Redis dependency. Otherwise you'll need to get this dependency and pass it to new SomeGuard using
const redis = app.get(getRedisToken())
https://docs.nestjs.com/guards#binding-guards
I am playing around with NestJS and I would like to take the error thrown from TypeORM and convert it into a shape that I can control.
Right now, I'm just trying to catch the error thrown from TypeORM and log it out to see that my custom filter is working correctly. But unfortunately, my console.log statement in the filter is never logging.
Here is a slimmed down version of my service and filter
user.service.ts
export class UserService {
constructor(
#InjectRepository(Users)
private readonly userRepository: Repository<Users>,
) {}
#UseFilters(new TypeOrmFilter())
async create(createUserDto: CreateUserDto) {
const user = this.userRepository.create(createUserDto);
return this.userRepository.save(user);
}
}
type-orm-filter.ts
#Catch()
export class TypeOrmFilter implements ExceptionFilter {
catch(exception: Error, host: ArgumentsHost) {
console.log('\nI have caught an error\n', exception);
throw exception;
}
}
Here is the log output from the error being thrown by TypeORM
[Nest] 61496 - 04/11/2021, 9:01:42 PM [ExceptionsHandler] invalid input syntax for type uuid: "123e4567" +2482ms
QueryFailedError: invalid input syntax for type uuid: "123e4567"
at new QueryFailedError (my-nest-project/error-project-nestjs/node_modules/typeorm/error/QueryFailedError.js:11:28)
at PostgresQueryRunner.<anonymous> (my-nest-project/error-project-nestjs/node_modules/typeorm/driver/postgres/PostgresQueryRunner.js:247:31)
at step (my-nest-project/error-project-nestjs/node_modules/typeorm/node_modules/tslib/tslib.js:141:27)
at Object.throw (my-nest-project/error-project-nestjs/node_modules/typeorm/node_modules/tslib/tslib.js:122:57)
at rejected (my-nest-project/error-project-nestjs/node_modules/typeorm/node_modules/tslib/tslib.js:113:69)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
You missed the await in UserService#create :p
tip: If you configure ESLint properly, this might never happen again because you already marked that method with await (https://eslint.org/docs/rules/require-await). Or just enforce typing the return (https://github.com/typescript-eslint/typescript-eslint/blob/v3.10.1/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md)