View as Json

API Response Formatter

Response Formatter provides a consistent, type-safe response structure for NestJS APIs.

Installation Guide

Run the following command:

npx servercn-cli@latest add response-formatter

Register the interceptor and filter

Register the interceptor and exception filter globally in your root module or bootstrap file.

Option 1: Global Setup (Recommended)
src/main.ts
import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from './app.module';
import {
  ResponseExceptionFilter,
  ResponseInterceptor,
} from './shared/response';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
 
  const reflector = app.get(Reflector);
  app.useGlobalInterceptors(new ResponseInterceptor(reflector));
  app.useGlobalFilters(new ResponseExceptionFilter());
 
  await app.listen(process.env.PORT ?? 8000);
}
void bootstrap();
Option 2: Per-Module Setup
src/users/users.controller.ts
import { Controller, UseFilters, UseInterceptors } from '@nestjs/common';
 
import {
  ResponseExceptionFilter,
  ResponseInterceptor,
} from '@/shared/response';
 
@Controller('users')
@UseInterceptors(ResponseInterceptor)
@UseFilters(ResponseExceptionFilter)
export class UsersController {}

Usage Examples

Return data directly from your controller — the interceptor handles wrapping:

src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
 
@Controller('health')
export class HealthController {
  @Get()
  check() {
    return { status: 'up', timestamp: new Date().toISOString() };
  }
}

Response:

{
  "success": true,
  "message": "Request successful",
  "statusCode": 200,
  "data": {
    "status": "up",
    "timestamp": "2026-08-28T03:16:19.757Z"
  }
}
src/users/users.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
 
import { ApiResponseConfig } from '@/shared/response';
 
@Controller('users')
export class UsersController {
  @Get(':id')
  @ApiResponseConfig({
    message: 'User found successfully',
  })
  findOne(@Param('id') id: string) {
    return { id, name: 'John Doe', email: 'john@example.com' };
  }
}

Response:

{
  "success": true,
  "message": "User found successfully",
  "statusCode": 200,
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "john@example.com"
  }
}

Return an ApiResponse instance directly to skip automatic wrapping:

src/users/users.controller.ts
import { Controller, Post } from '@nestjs/common';
import { ApiResponse } from '@/shared/response';
 
@Controller('users')
export class UsersController {
  @Post()
  create() {
    const user = {
      id: 'usr_123',
      name: 'John Doe',
      username: 'john_doe',
      email: 'john@example.com',
    };
 
    return ApiResponse.created('User created successfully', user);
  }
}

Response:

{
  "success": true,
  "message": "User created successfully",
  "statusCode": 201,
  "data": {
    "id": "usr_123",
    "name": "John Doe",
    "username": "john_doe",
    "email": "john@example.com"
  }
}

Throw standard NestJS exceptions — the filter handles formatting:

src/users/users.controller.ts
import { BadRequestException, Controller, Get, Param } from '@nestjs/common';
 
@Controller('users')
export class UsersController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    throw new BadRequestException('Invalid User ID');
  }
}

Response:

{
  "success": false,
  "message": "Invalid User ID",
  "statusCode": 400,
  "errors": {
    "message": "Invalid User ID",
    "error": "Bad Request",
    "statusCode": 400
  }
}
src/posts/posts.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { ApiResponse, PaginationMeta } from '@/src/shared/response';
 
@Controller('posts')
export class PostsController {
  @Get()
  findAll(@Query('page') page = 1, @Query('limit') limit = 10) {
    const { data, total } = this.postsService.paginate(page, limit);
    
    const meta: PaginationMeta = {
      page: Number(page),
      limit: Number(limit),
      total,
      totalPages: Math.ceil(total / limit),
      hasNextPage: page * limit < total,
      hasPreviousPage: page > 1,
    };
    
    return ApiResponse.ok('Posts retrieved', data, meta);
  }
}

Response:

{
  "success": true,
  "message": "Posts retrieved",
  "statusCode": 200,
  "data": [...],
  "meta": {
    "page": 1,
    "limit": 10,
    "total": 45,
    "totalPages": 5,
    "hasNextPage": true,
    "hasPreviousPage": false
  }
}

File & Folder Structure

Loading files...

Installation

npx servercn-cli@latest add response-formatter