Tips for preparing your response for delivery in Nest.js?

After carefully following the documentation, I successfully implemented an interceptor for response mapping.

I am aiming to establish a uniform JSON format for all responses.

Is there a more effective way to achieve this rather than using an interceptor?

{
  "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 };
      }
}

Answer №1

If I comprehend correctly the purpose of your controller response and your overall interceptor response, a similar approach can be taken:

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
          }
        })),
      );
  }
}

Ensure that your controller returns

{message: 'Custom message', result}
.

Alternatively, you could create a custom decorator that extracts a value (message) from the class and method, using it in the interceptor after injecting the reflector, although this approach would demand more setup initially.

Answer №2

Instead of making it mandatory for every controller to have a message property in the response data, I took advantage of reflectors and SetMetadata to extract the message value and assign it as metadata on the controller method.

response_message.decorator.ts

import { SetMetadata } from '@nestjs/common';

export const ResponseMessage = (message: string) =>
      SetMetadata('response_message', message);

response.interceptor.ts

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
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>>
{
  constructor(private reflector: Reflector) {}
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next.handle().pipe(
      map((data) => ({
        statusCode: context.switchToHttp().getResponse().statusCode,
        message:
          this.reflector.get<string>(
            'response_message',
            context.getHandler(),
          ) || '',
        data,
      })),
    );
  }
}

Usage in controller

media.controller.ts

 @Get('/stats')
  @ResponseMessage('Fetched Stats Successfully')
  getUserMediaStats(@GetUser('id') userId: Types.ObjectId) {
    return this.mediaService.getUserMediaStats(userId);
  }

My Response Structure

{
    "statusCode": 200,
    "message": "Fetched Stats Successfully",
    "data": {
        "userId": "6419cbb6c053f0692ef400ae",
        "totalCount": 8,
        "totalSizeMB": 2.079585,
        "imageCount": 8,
        "videoCount": 0
    }
}

Answer №3

I attempted to utilize the solutions provided above

src\interceptors\transform.interceptor.ts

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

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

@Injectable()
export class TransformationInterceptor<T> implements NestInterceptor<T, 
Response<T>> {
constructor(private reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler): 
Observable<Response<T>> {
   return next.handle().pipe(
   map((data) => ({
    message: this.reflector.get<string>('response_message', 
      context.getHandler()) || data.message || '',
    statusCode: context.switchToHttp().getResponse().statusCode,
    data: data.result || data
   }))
   );
 }
}

src\employees\employees.controller.ts

@Get()
@ResponseMessage('Employees records fetched Successfully')
findAll() {
  return this.employeesService.findAll();
}

Response structure

 {
   "message": "Employees records fetched Successfully",
   "statusCode": 200,
   "data": [
     {
       "id": 1,
       "code": "2001",
       "companyCode": "01",
       "firstName": "Walter",
       "lastName": "Mendoza",
       "email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="94e3baf0e1f9f9edd4f3f9f5fdf8baf7fbf9">[email protected]</a>",
       "phone": "+1214",
       "siteId": 4,
       "roleId": 5,
       "isActive": 0,
       "inviteStatus": 3,
       "shiftId": null,
       "createdAt": "2019-08-26T15:21:44.000Z",
       "syncAt": "2022-10-09T19:01:40.000Z",
       "modifiedAt": "2022-10-09T19:01:40.000Z"
     },
     {
      "id": 2,
      "code": "2002",
      "companyCode": "01",
      "firstName": "Hugo",
      "lastName": "Rosa",
      "email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="1074657d7d6950777d71797c3e737f7d">[email protected]</a>",
      "phone": "+18324888233",
      "siteId": 4,
      "roleId": 5,
      "isActive": 1,
      "inviteStatus": 3,
      "shiftId": null,
      "createdAt": "2019-08-26T15:21:44.000Z",
      "syncAt": "2022-10-09T19:01:40.000Z",
      "modifiedAt": "2022-10-09T19:01:40.000Z"
     }
    ] 
   }



 @Get(':id')
 async findOne(@Param('id') id: string) {
     const result = await this.employeesService.findOne(+id);
     return { message: `Employees ${id} detail fetched Successfully`, 
     result};
 }

Response Structure

{
 "message": "Employees 1 detail fetched Successfully",
 "statusCode": 200,
 "data": {
  "id": 1,
  "code": "2001",
  "companyCode": "01",
  "firstName": "Walter",
  "lastName": "Mendoza",
  "email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="cbafbea6a6b28baca6aaa2a7e5a8a4a6">[email protected]</a>",
  "phone": "+1214",
  "siteId": 4,
  "roleId": 5,
  "isActive": 0,
  "inviteStatus": 3,
  "shiftId": null,
  "createdAt": "2019-08-26T15:21:44.000Z",
  "syncAt": "2022-10-09T19:01:40.000Z",
  "modifiedAt": "2022-10-09T19:01:40.000Z"
 }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Navigate to the initial error on a form submission in a Reactjs application containing numerous form fields

I am working on a project using React along with Material UI and TypeScript, where I have implemented a form. Upon form submission, if there are validation errors in any input fields, I would like the page to automatically scroll to the first input field w ...

Utilize generic typings to interact with the Array object

I'm facing a challenge in developing an interface that is dependent on another interface. Everything was going smoothly until I reached the Array of Objects. Let me elaborate, I have an 'Entity' that defines how a document is stored in a ...

Error encountered in jsonwebtoken payload

Working on a web application with nodejs and angular cli, I have implemented JWT for login authentication. However, during the processing, I encountered the following error: Error: Expected "payload" to be a plain object. at validate (D:\Mean ...

What is the best way to prioritize items on a list in JavaScript?

Looking to organize your to-do list items by priority? In this task list, users can enter an item, select a priority level, and add it to the list. Here is an example HTML form: <input id="task" type="text"/> <select id="priority"> <o ...

Are the Angular App routerlinks functioning properly in development but encountering issues in the production environment?

Every time I execute the command npm start, my Angular application works perfectly as intended. However, when I attempt to build it in "prod" mode, my application doesn't seem to function properly. It only displays a static page. All navigation link ...

Utilize a solo input field to upload either a video or image, and showcase a preview of the uploaded content in a

I'm currently working on creating an input field that allows for the upload of both images and videos. Although I am able to successfully upload the files, I'm encountering an error when trying to display a preview. The code snippet below outline ...

Incorporating NestJS into the express framework causes Nest to restart

As we continue the process of migrating our application from the NestJS framework to Express, we are considering incorporating NestJS as a module within Express. This phased migration approach raises concerns about the restart of NestJS for every request. ...

Error copying cell width attribute when using border collapse

Currently, I am attempting to duplicate a table header by copying the tr HTML and then replicating the th widths. Unfortunately, this method is unsuccessful as the widths are displayed as (width minus W) pixels when border: 'Wpx' and border-colla ...

The powerful combination of Visual Studio 2015, TypeScript, Cordova, Angular 2, and System

I am encountering an issue with the loading of external modules using systemJS. I have created a small sample project for VS2015. Feel free to check out the code here: https://github.com/dbiele/TypeScript-Cordova-SystemJS After building the project and at ...

Confirm that the input value consists of x numeric characters

Looking to validate an input field that must contain a minimum of x amount of numeric characters. For example, let's say I require the input value to have at least 5 numeric characters: 12345 - valid AB12345 - valid 123456 - valid AB312312 - valid a ...

Display an "add to cart" button and a discount image when hovering over

Currently, I am in the process of developing an Online Shopping website using PHP. To enhance the design of my website, I have implemented bootstrap. One specific query I have is how to display an 'add to cart' button and a discount image when a ...

What is the process of emphasizing a WebElement in WebdriverIO?

Is there a way to highlight web elements that need to be interacted with or asserted in webdriverIO? Or is there a JavaScript code similar to the javascript executor in Selenium that can be used for this purpose? ...

Filter the array and determine the number of elements in the filtered array

I am looking to filter the contents of two arrays and then count the elements where "isimplemented: 'Yes'" is true: const array1 = [{ProjectName: "IT", Department: "Software"}] const array2 = [{Name: "IT", isimplemented: "Yes"}] The method I at ...

Can one validate a single route parameter on its own?

Imagine a scenario where the route is structured as follows: companies/{companyId}/departments/{departmentId}/employees How can we validate each of the resource ids (companyId, departmentId) separately? I attempted the following approach, but unfortunate ...

Angular generates an array that is not native to the system

When I directly set vm.files in my view using the following code: <input type="file" ng-model= vm.files[0]> <input type="file" ng-model= vm.files[1]> The contents of vm.files are displayed as shown in example A: https://i.stack.imgur.com/K3V ...

Radio Button Fail to Work as Expected due to Missing Attribute

I've attempted various suggested solutions for this issue, such as using the required attribute on one input and setting it to "required." I'm currently unsure of the next steps to take. Any advice on resolving this problem would be greatly appre ...

Setting the z-index for Bootstrap dropdown menus on containers that are generated dynamically

When using the Bootstrap 3 dropdown-menu within a dynamically generated container, I am encountering an issue where the dropdown-menu appears behind the newly created elements. Please refer to the image for visual clarification. The container item has pos ...

Interconnected Dropdown Menus

I've implemented the cascading dropdown jQuery plugin available at https://github.com/dnasir/jquery-cascading-dropdown. In my setup, I have two dropdowns named 'Client' and 'Site'. The goal is to dynamically reduce the list of si ...

Errors with particle visibility in tsparticles nextjs

I am trying to incorporate a particle background into my NextJS application by using the command yarn add react-tsparticles. However, although the tsparticles package successfully added the canvas element to my app, I am unable to see the particles themsel ...

The computed function is unable to find the property on the specified type

I am a beginner in TypeScript. I have encountered an issue with the code below, which runs perfectly fine in JavaScript but is not compiling here. export default { data: function() { return { data: [ 'Angular', 'A ...