Response Documentation
This documentation explains the features and usage of Response Module: Located at src/common/response
Overview
Complete NestJS Boilerplate standardizes API responses through decorators that automatically format responses, handle pagination, manage file downloads, and set custom headers. Each decorator uses an interceptor to transform data into consistent structures with metadata, status codes, and localized messages.
Table of Contents
Related Documents
- Message Documentation - For internationalization and error message translation
- Handling Error Documentation - For exception handling and response formatting
- Doc Documentation - For API documentation integration with DTOs
- File Upload Documentation - For file validation pipes
Response Decorators
@Response
Standard API response decorator with optional caching.
Parameters:
messagePath(string): Path to response message for localizationoptions(optional): Configuration optionscache(boolean | object): Enable caching
Interceptor: ResponseInterceptor - transforms responses into standard format with metadata and localized messages via MessageService
Usage:
@Response('user.get')
@Get('/:id')
async getUser(@Param('id') id: string): Promise<IResponseReturn<UserDto>> {
return {
data: await this.userService.findById(id)
};
}Custom Status Code:
@Response('user.create')
@Post('/')
async createUser(@Body() dto: CreateUserDto): Promise<IResponseReturn<UserDto>> {
try {
const data = await this.userService.create(dto);
// Response: { statusCode: 201, message: "...", data: {...}, metadata: {...} }
return {
data,
metadata: {
statusCode: 201,
httpStatus: HttpStatus.CREATED
}
};
} catch {
// Response: { statusCode: 200, message: "...", data: {...}, metadata: {...} }
return {
data,
metadata: {
statusCode: 200,
httpStatus: HttpStatus.OK
}
};
}
}Custom Message:
@Response('user.update')
@Patch('/:id')
async updateUser(@Param('id') id: string, @Body() dto: UpdateUserDto): Promise<IResponseReturn<UserDto>> {
const user = await this.userService.update(id, dto);
return {
data: user,
metadata: {
messagePath: 'user.updateSuccess',
messageProperties: { name: user.name }
}
};
}@ResponsePaging
Paginated API response decorator with optional caching. Supports both offset-based and cursor-based pagination.
Parameters:
messagePath(string): Path to response message for localizationoptions(optional): Configuration optionscache(boolean | object): Enable caching
Requirements:
- Request must include pagination parameters (see Pagination Documentation)
- Response must implement
IResponsePagingReturn<T>interface - Must specify pagination
type:'offset'or'cursor'
Interceptor: ResponsePagingInterceptor - validates pagination data, supports offset and cursor-based pagination, includes search/filter/sort metadata
Offset-based Pagination:
@ResponsePaging('user.list')
@Get('/list')
async listUsers(
@PaginationQuery() { page, perPage, orderBy, orderDirection }: PaginationListDto
): Promise<IResponsePagingReturn<UserDto>> {
const { data, totalPage, count } = await this.userService.findAll({
page,
perPage,
orderBy,
orderDirection
});
return {
type: 'offset',
data,
totalPage,
page,
perPage,
count,
hasNext: page < totalPage,
nextPage: page < totalPage ? page + 1 : undefined,
previousPage: page > 1 ? page - 1 : undefined
};
}Cursor-based Pagination:
@ResponsePaging('user.list')
@Get('/list')
async listUsers(
@PaginationQuery() query: PaginationListDto
): Promise<IResponsePagingReturn<UserDto>> {
const { data, cursor, count, hasNext } = await this.userService.findAllCursor(query);
return {
type: 'cursor',
data,
cursor,
perPage: query.perPage,
count,
hasNext
};
}@ResponseFile
File download response decorator that handles CSV and PDF file downloads with proper headers and streaming.
Parameters: None
Requirements:
- Response must implement
IResponseFileReturninterface (union ofIResponseCsvReturn|IResponsePdfReturn) - Must specify
extension:EnumFileExtensionDocument.csvorEnumFileExtensionDocument.pdf - CSV data must be a string (pre-converted to CSV format)
- PDF data must be a Buffer
- Optional
filename- if not provided, generates timestamped filename:export-{timestamp}.{extension}
Interceptor: ResponseFileInterceptor - validates data based on extension type, converts to Buffer, sets content headers (Content-Type, Content-Disposition, Content-Length), returns StreamableFile
CSV Export (Auto-generated Filename):
@ResponseFile()
@Get('/export/csv')
async exportUsersCsv(): Promise<IResponseCsvReturn> {
const csvData = this.fileService.writeCsv(users);
return {
data: csvData,
extension: EnumFileExtensionDocument.csv
// Filename will be: export-{timestamp}.csv
};
}CSV with Custom Filename:
@ResponseFile()
@Get('/export/users')
async exportUsersCustom(): Promise<IResponseCsvReturn> {
const users = await this.userService.findAll();
const csvData = this.fileService.writeCsv(users);
return {
data: csvData,
extension: EnumFileExtensionDocument.csv,
filename: 'users-export.csv'
};
}CSV with Formatted Data:
@ResponseFile()
@Get('/export/report')
async exportUsersReport(): Promise<IResponseCsvReturn> {
const users = await this.userService.findAll();
const formattedData = users.map(user => ({
Name: user.name,
Email: user.email,
'Created At': new Date(user.createdAt).toLocaleDateString(),
Status: user.isActive ? 'Active' : 'Inactive'
}));
const csvData = this.fileService.writeCsv(formattedData);
return {
data: csvData,
extension: EnumFileExtensionDocument.csv,
filename: 'user-report.csv'
};
}PDF Export:
@ResponseFile()
@Get('/export/pdf')
async exportUsersPdf(): Promise<IResponsePdfReturn> {
const users = await this.userService.findAll();
// Generate PDF using external library (e.g., pdfkit, puppeteer, jsPDF)
// Example: const pdfBuffer = await generatePdfReport(users);
const pdfBuffer = Buffer.from('...'); // Your PDF generation logic here
return {
data: pdfBuffer,
extension: EnumFileExtensionDocument.pdf,
filename: 'users-report.pdf'
};
}Dynamic Format Export:
@ResponseFile()
@Get('/export')
async exportUsers(@Query('format') format: 'csv' | 'pdf'): Promise<IResponseFileReturn> {
const users = await this.userService.findAll();
const timestamp = Date.now();
if (format === 'pdf') {
// Generate PDF buffer using your preferred PDF library
const pdfBuffer = Buffer.from('...'); // Your PDF generation logic
return {
data: pdfBuffer,
extension: EnumFileExtensionDocument.pdf,
filename: `users-${timestamp}.pdf`
};
}
const csvData = this.fileService.writeCsv(users);
return {
data: csvData,
extension: EnumFileExtensionDocument.csv,
filename: `users-${timestamp}.csv`
};
}Response Structure
Standard
{
statusCode: number;
message: string;
metadata: {
language: string;
timestamp: number;
timezone: string;
path: string;
version: string;
repoVersion: string;
requestId: string;
correlationId: string;
};
data?: T;
}Paginated
{
statusCode: number;
message: string;
metadata: {
// Base metadata
language: string;
timestamp: number;
timezone: string;
path: string;
version: string;
repoVersion: string;
requestId: string;
correlationId: string;
// Pagination metadata
type: 'offset' | 'cursor'; // Pagination type
search?: string;
filters?: Record<string, any>;
perPage: number;
// Offset-specific fields (when type = 'offset')
page?: number;
totalPage?: number;
count?: number;
nextPage?: number;
previousPage?: number;
// Cursor-specific fields (when type = 'cursor')
nextCursor?: string;
previousCursor?: string;
count?: number; // Optional, included if requested
// Common fields
hasNext: boolean;
orderBy: string;
orderDirection: 'asc' | 'desc';
availableSearch: string[];
availableOrderBy: string[];
};
data: T[];
}Activity Log Metadata (Optional)
All response types (IResponseReturn, IResponsePagingReturn, IResponseFileReturn) support optional activity log metadata for request tracking and auditing:
return {
data: user,
metadataActivityLog: {
// Activity log tracking data
}
};See Activity Log Documentation for complete implementation details.
Caching
@Response and @ResponsePaging support optional caching via ResponseCacheInterceptor (extends NestJS CacheInterceptor with custom prefixes).
Basic Caching:
@Response('user.get', { cache: true })
@Get('/:id')
async getUser(@Param('id') id: string): Promise<IResponseReturn<UserDto>> {
return { data: await this.userService.findById(id) };
}Cache Key:
Apis:*Custom Cache Configuration:
@Response('user.get', {
cache: {
key: 'user-detail',
ttl: 3600 // seconds
}
})
@Get('/:id')
async getUser(@Param('id') id: string): Promise<IResponseReturn<UserDto>> {
return { data: await this.userService.findById(id) };
}See NestJS Cache Manager and Cache Documentation for configuration.
Custom Headers
All responses automatically include these headers (set by interceptors):
x-custom-lang: Response languagex-timestamp: Response timestampx-timezone: Response timezonex-version: API versionx-repo-version: Repository versionx-request-id: Unique request identifierx-correlation-id: Request correlation identifier