API Response Handling
The API Response Formatter component provides a consistent, predictable, and type-safe response structure for Express-based APIs.
It enforces a unified response contract across your entire backend, improving:
- Client-side reliability
- API readability
- Error handling consistency
- Developer experience
This component is designed to work seamlessly with:
- Async Handler
- ApiError
- Centralized error middleware
- Frontend API clients
Installation Guide
This component requires additional ServerCN components.
👉 Note: You do not need to install any servercn dependencies manually. Installing this component will automatically install all required servercn dependencies. Manual installation is optional if you prefer to manage dependencies yourself.
1. Install ServerCN dependencies(Optional)
npx servercn add http-status-codesDocumentation: HTTP Status Codes
2. Install this component
npx servercn add response-formatter⚠️ If this dependency is not installed, the component will not function correctly.
The Problem It Solves
In many Express applications, responses quickly become inconsistent:
res.json(data);
res.status(201).send(user);
res.status(400).json({ error: "Invalid input" });Basic Implementation
1. MVC Structure
src/utils/api-response.ts
import { STATUS_CODES, StatusCode } from "../constants/status-codes";
import type { Response } from "express";
type ApiResponseParams<T> = {
success: boolean;
message: string;
statusCode: StatusCode;
data?: T | null;
errors?: unknown;
};
export class ApiResponse<T = unknown> {
public readonly success: boolean;
public readonly message: string;
public readonly statusCode: StatusCode;
public readonly data?: T | null;
public readonly errors?: unknown;
constructor({
success,
message,
statusCode,
data = null,
errors
}: ApiResponseParams<T>) {
this.success = success;
this.message = message;
this.statusCode = statusCode;
this.data = data;
this.errors = errors;
}
send(res: Response): Response {
return res.status(this.statusCode).json({
success: this.success,
message: this.message,
statusCode: this.statusCode,
...(this.data !== undefined && { data: this.data }),
...(this.errors !== undefined && { errors: this.errors })
});
}
static Success<T>(
res: Response,
message: string,
data?: T,
statusCode: StatusCode = STATUS_CODES.OK
): Response {
return new ApiResponse<T>({
success: true,
message,
data,
statusCode
}).send(res);
}
static ok<T>(res: Response, message = "OK", data?: T) {
return ApiResponse.Success(res, message, data, STATUS_CODES.OK);
}
static created<T>(res: Response, message = "Created", data?: T) {
return ApiResponse.Success(res, message, data, STATUS_CODES.CREATED);
}
}
/*
* Usage:
* ApiResponse.ok(res, "OK", data);
* ApiResponse.created(res, "Created", data);
*/2. Feature Structure
src/shared/utils/api-response.ts
import { STATUS_CODES, StatusCode } from "../constants/status-codes";
import type { Response } from "express";
type ApiResponseParams<T> = {
success: boolean;
message: string;
statusCode: StatusCode;
data?: T | null;
errors?: unknown;
};
export class ApiResponse<T = unknown> {
public readonly success: boolean;
public readonly message: string;
public readonly statusCode: StatusCode;
public readonly data?: T | null;
public readonly errors?: unknown;
constructor({
success,
message,
statusCode,
data = null,
errors
}: ApiResponseParams<T>) {
this.success = success;
this.message = message;
this.statusCode = statusCode;
this.data = data;
this.errors = errors;
}
send(res: Response): Response {
return res.status(this.statusCode).json({
success: this.success,
message: this.message,
statusCode: this.statusCode,
...(this.data !== undefined && { data: this.data }),
...(this.errors !== undefined && { errors: this.errors })
});
}
static Success<T>(
res: Response,
message: string,
data?: T,
statusCode: StatusCode = STATUS_CODES.OK
): Response {
return new ApiResponse<T>({
success: true,
message,
data,
statusCode
}).send(res);
}
static ok<T>(res: Response, message = "OK", data?: T) {
return ApiResponse.Success(res, message, data, STATUS_CODES.OK);
}
static created<T>(res: Response, message = "Created", data?: T) {
return ApiResponse.Success(res, message, data, STATUS_CODES.CREATED);
}
}
/*
* Usage:
* ApiResponse.ok(res, "OK", data);
* ApiResponse.created(res, "Created", data);
*/Usage Example
import { Request, Response } from "express";
import { ApiResponse } from "../utils/api-response";
export async function healthCheck(_req: Request, res: Response) {
return ApiResponse.ok(res, "Service is healthy");
}Response
{
"success": true,
"message": "Service is healthy",
"statusCode": 200
}