Search code examples
javascripttypescriptnestjs

How to format response before sending in Nest.js?


I followed the documentation and was able to add an interceptor for response mapping.

I want a consistent json format output for responses.

How can I achieve this with interceptor or with something else better than this approach.

{
  "statusCode": 201,
  "message": "Custom Dynamic Message"
  "data": {
     // properties
     meta: {}
  }
}

transform.interceptor.ts

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
  statusCode: number;
  data: T;
}

@Injectable()
export class TransformInterceptor<T>
  implements NestInterceptor<T, Response<T>> {
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next
      .handle()
      .pipe(
        map((data) => ({
          statusCode: context.switchToHttp().getResponse().statusCode,
          data,
        })),
      );
  }
}

app.controller.ts

export class AppController {
      @Post('login')
      @UseGuards(AuthGuard('local'))
      @ApiOperation({ summary: 'Login user' })
      @ApiBody({ type: LoginDto })
      @ApiOkResponse({ content: { 'application/json': {} } })
      @UseInterceptors(TransformInterceptor)
      async login(@Request() req) {
        const result = await this.authService.login(req.user);
        return { message: 'Thank you!', result };
      }
}

Solution

  • If I am understanding what you are doing with your controller response and your overall interceptor response, what you can do is something similar:

    import {
      Injectable,
      NestInterceptor,
      ExecutionContext,
      CallHandler,
    } from '@nestjs/common';
    import { Observable } from 'rxjs';
    import { map } from 'rxjs/operators';
    
    export interface Response<T> {
      statusCode: number;
      message: string;
      data: T;
    }
    
    @Injectable()
    export class TransformInterceptor<T>
      implements NestInterceptor<T, Response<T>> {
      intercept(
        context: ExecutionContext,
        next: CallHandler,
      ): Observable<Response<T>> {
        return next
          .handle()
          .pipe(
            map((data) => ({
              statusCode: context.switchToHttp().getResponse().statusCode,
              message: data.message
              data: {
                result: data.result,
                meta: {} // if this is supposed to be the actual return then replace {} with data.result
              }
            })),
          );
      }
    }
    

    And keep your controller return as {message: 'Custom message', result}.

    Another option, which would require more upfront but possible allow for cleaner code would be to create a custom decorator that reflected a value (message) from the class and method and then retrieved that value in the interceptor after injecting the reflector, but again, that would take more upfront effort to set up.