@backendly/api-response
v1.0.1
Published
Standardized, type-safe API response helpers for Express, Fastify, and NestJS
Maintainers
Readme
@backendly/api-response
Standardized, type-safe API response helpers for Express, Fastify, and NestJS.
Stop repeating res.status(...).json({...}) in every controller. Use a consistent response format across your entire API.
Features
- TypeScript-first with strict typing and generics
- Framework-agnostic core with Express, Fastify, and NestJS adapters
- ESM + CommonJS dual package exports
- Unified success and error response shapes
- Built-in pagination metadata computation
- Validation normalizers for express-validator, Joi, Zod, and Yup
- Request ID and timestamp support
- Custom error classes with global error handler
- Zero runtime dependencies in the core
- Node.js 20+
Installation
npm install @backendly/api-responseOptional peer dependencies (install only what you use):
npm install express # Express adapter
npm install fastify # Fastify adapter
npm install @nestjs/common # NestJS adapterQuick Start
Express
import express from 'express';
import { createResponse, createExpressSender } from '@backendly/api-response';
const app = express();
const { response, middleware } = createResponse();
app.use(middleware.requestContext());
app.get('/users/:id', (req, res) => {
response.bind(req, createExpressSender(res)).success(
{ id: req.params.id, name: 'Jane' },
{ message: 'User fetched successfully.' },
);
});Fastify
import Fastify from 'fastify';
import { createResponse } from '@backendly/api-response';
import { createFastifySender } from '@backendly/api-response/fastify';
const app = Fastify();
const { response } = createResponse();
app.get('/users', async (req, reply) => {
response.bind(req, createFastifySender(reply)).success([{ id: 1 }]);
});NestJS
import { Controller, Get, Req, Res } from '@nestjs/common';
import { createNestResponseService } from '@backendly/api-response/nestjs';
const responseService = createNestResponseService();
@Controller('users')
export class UsersController {
@Get()
findAll(@Req() req, @Res() res) {
responseService.bind(req, res).success([{ id: 1 }]);
}
}Response Format
Success
{
"success": true,
"statusCode": 200,
"message": "User fetched successfully.",
"data": { "id": "1", "name": "Jane" },
"meta": {},
"timestamp": "2026-07-07T10:00:00.000Z",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}Error
{
"success": false,
"statusCode": 404,
"message": "User not found.",
"error": {
"code": "NOT_FOUND",
"details": {}
},
"timestamp": "2026-07-07T10:00:00.000Z",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}API Methods
Use response.bind(req, sender) then call:
| Method | HTTP Status |
|--------|-------------|
| success(data, options?) | 200 |
| created(data, options?) | 201 |
| accepted(data?, options?) | 202 |
| noContent() | 204 |
| badRequest(options?) | 400 |
| unauthorized(options?) | 401 |
| forbidden(options?) | 403 |
| notFound(options?) | 404 |
| conflict(options?) | 409 |
| unprocessable(options?) | 422 |
| tooManyRequests(options?) | 429 |
| serverError(options?) | 500 |
| validation(source, options?) | 422 |
| paginated(data, pagination, options?) | 200 |
| download(file, options?) | 200 |
| redirect(url, code?) | 3xx |
| custom(statusCode, body) | Custom |
Configuration
const { response, middleware } = createResponse({
includeTimestamp: true,
includeRequestId: true,
includeMeta: true,
defaultMessages: true,
language: 'en',
pretty: false,
debug: process.env.NODE_ENV !== 'production',
apiVersion: '1.0.0',
requestIdHeader: 'x-request-id',
});Pagination
response.bind(req, sender).paginated(
users,
{ page: 2, limit: 10, total: 45 },
{ message: 'Users fetched successfully.' },
);Auto-generated meta:
{
"pagination": {
"page": 2,
"limit": 10,
"total": 45,
"totalPages": 5,
"hasNext": true,
"hasPrevious": true,
"nextPage": 3,
"previousPage": 1
}
}Validation Errors
Supports express-validator, Joi, Zod, Yup, and custom formats:
import { normalizeValidation } from '@backendly/api-response/validators';
// Zod example
try {
schema.parse(body);
} catch (error) {
response.bind(req, sender).validation(error);
}Unified error details:
{
"error": {
"code": "VALIDATION_ERROR",
"details": {
"count": 2,
"errors": [
{ "field": "email", "message": "Invalid email" },
{ "field": "password", "message": "Too short" }
]
}
}
}Custom Errors
import { NotFoundError, ValidationError } from '@backendly/api-response/errors';
throw new NotFoundError('User not found.');
// Or handle in middleware
response.bind(req, sender).handleError(error);Request ID
Resolved in order:
req.id(Fastify)req.requestIdx-request-idheader- Auto-generated UUID
FAQ
Does 204 No Content include a JSON body? No. It follows HTTP standards with an empty body.
What status code is used for validation? 422 Unprocessable Entity — the standard for semantic validation failures.
Can I disable timestamps?
Yes: createResponse({ includeTimestamp: false }).
Does it work with ESM and CommonJS? Yes. Both are supported via conditional exports.
Contributing
Contributions are welcome! Please open an issue or PR.
- Fork the repository
- Create a feature branch
- Run
npm testandnpm run lint - Submit a pull request
License
MIT © Muzammil
