I have a nestjs-graphql project. I use class-validator
and nestjs-i18n
module.
I can use i18nService
when injected in a service as intended. What I'm struggling to do however is to use i18n in my ExceptionFilter
to return translated message from the ValidationPipe
handled by class-validator
What I currently have
//app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ItemsModule } from './items/items.module';
import { GraphQLModule } from '@nestjs/graphql';
import { MongooseModule } from '@nestjs/mongoose';
import { ConfigModule } from '@nestjs/config';
import { I18nModule, I18nJsonParser } from 'nestjs-i18n';
import configuration from './config/configuration';
import * as path from 'path';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, load: [configuration] }),
MongooseModule.forRoot(process.env.MONGO_URI),
I18nModule.forRoot({
fallbackLanguage: 'en',
parser: I18nJsonParser,
parserOptions: { path: path.join(__dirname, '/i18n/') },
}),
GraphQLModule.forRoot({
autoSchemaFile: 'schema.gql',
playground: true,
introspection: true,
context: ({ req, connection }) =>
connection ? { req: connection.context } : { req },
}),
ItemsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
//main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { AllExceptionsFilter } from './utils/exceptions.filters';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ transform: true }));
app.useGlobalFilters(new AllExceptionsFilter(new Logger('Exceptions')));
app.enableCors();
const port = process.env.PORT || 3000;
await app.listen(port);
}
bootstrap();
//AllExceptionFilters.ts
import {
ExceptionFilter,
Catch,
HttpException,
HttpStatus,
Logger,
} from '@nestjs/common';
import { ApolloError } from 'apollo-server-errors';
import { MongoError } from 'mongodb';
@Catch(HttpException)
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private logger: Logger) {}
async catch(exception: HttpException) {
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const exceptionMessage = (exception) =>
exception instanceof MongoError
? exception?.message
: exception?.response?.message;
this.logger.error(exceptionMessage(exception), exception.stack);
throw new ApolloError(exceptionMessage(exception), status.toString());
}
}
My idea was to pass the i18n key to the class to validate :
import { Field, InputType, } from '@nestjs/graphql';
import { Length } from 'class-validator';
@InputType()
export class ItemToValidate {
@Length(5, 30, { message: 'global.length' }) //i18n Key
@Field()
readonly title: string;
}
... to use it in AllExceptionsFilter
as I would in a service:
@Catch(HttpException)
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private logger: Logger, private i18n: I18nService) {}
async catch(exception: HttpException) {
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const exceptionMessage = (exception) =>
exception instanceof MongoError
? exception?.message
: exception?.response?.message;
const translatedMessage = await this.i18n.translate(
exceptionMessage(exception),
);
...
}
}
However, I have a logical error instantiating the Filter class in the boostrap function as I don't know how to access I18nService and inject it from there :
async function bootstrap() {
const logger = new Logger('bootstrap');
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ transform: true }));
app.useGlobalFilters(new AllExceptionsFilter(new Logger('Exceptions'), /*I18nService ?*/ ));
}
bootstrap();
What's the best way to go to achieve this?
As shown in the docs you cannot have dependency injection if you register your filter with
useGlobalFilters()
.Instead you have to do something like this: