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?