express-unified-response
v1.0.2
Published
Production-ready Express API response standardizer. Features centralized error handling, Mongoose error normalization, smart pagination, and DTO transforms.
Maintainers
Readme
express-unified-response
🚀 Express Smart Response & Error Toolkit
A production-ready response, error handling, and pagination toolkit for Express + Mongoose APIs. It enforces consistent API responses, centralized error handling, and powerful pagination with minimal boilerplate.
✨ FEATURES
✅ Unified API responses
- Standard success, created, updated, deleted responses
- Configurable response keys (success, data, meta, message, error)
- Built-in REST semantics (204 No Content, 201 Created, etc.)
❌ Centralized error handling
- Custom AppError hierarchy
- Automatic conversion of Mongoose errors (Validation, Cast, Duplicate Key).
- Handles JWT, File Upload, and Axios errors automatically.
- Safe defaults for production (no stack leaks)
📄 Pagination (Query + Aggregate)
- Paginate standard Mongoose queries and aggregation pipelines.
- Supports DTO / Transform functions to clean data.
- Enforces
maxLimitautomatically to protect your database.
🧠 Smart Middleware Extensions
Methods are attached directly to the res object: res.success(), res.created(), res.updated(), res.deleted(), res.list(), res.paginateQuery(), res.paginateAggregate().
⚙️ Fully configurable
- Rename response keys
- Customize pagination labels
- Control REST defaults
- Plug in custom logging
- Enable/disable stack traces
📦 Installation
npm install express-unified-responseRequirements
- Node.js ≥ 20
- Express
- Mongoose
🔧 Basic Setup
import express from "express";
import {
createResponseMiddleware,
createErrorMiddleware,
} from "express-unified-response";
const app = express();
app.use(express.json());
// Attach response helpers
app.use(createResponseMiddleware());
// Your routes go here...
// Central error handler (must be last)
app.use(createErrorMiddleware());
export default app;🟢 USING RESPONSE HELPERS
The library follows two intuitive argument patterns:
Standard: (Data, Message, Options)
Paginated: (Data/Model, Options, Message)
Success & Created
// Simple usage
res.success(user, "User fetched successfully");
// With Transform (DTO) and Silent Logging
res.success(user, "OK", {
transform: (u) => ({ id: u._id, name: u.name }),
silent: true,
});
res.created(newUser, "User created");Updated & Deleted
The toolkit handles REST semantics automatically. If data is provided, it returns 200. If data is null/empty, it returns 204.
// Returns 200 + Body
res.updated(updatedUser, "User updated");
// Returns 204 No Content (No body)
res.updated(null);
// Message-only: Returns 200 + Message (Skips data when 1st arg is null/undefined/`_`)
res.updated(null, "Password changed successfully");
res.deleted(null, "User deleted");List (Paginated or Non Paginated)
Standardizes array responses. Non-paginated lists still receive a meta block for frontend consistency.
res.list(
users,
{
paginate: true,
page: 1,
limit: 10,
transform: (u) => ({ id: u._id, name: u.name }),
},
"Users fetched successfully"
);Paginated Query & Aggregate
await res.paginateQuery(
UserModel,
{
page: 1,
limit: 10,
filter: { isActive: true },
populate: "profile",
transform: (doc) => ({ id: doc._id, email: doc.email }),
},
"Active users fetched"
);
await res.paginateAggregate(
UserModel,
{
pipeline: [{ $match: { score: { $gt: 80 } } }],
transform: (doc) => ({ id: doc._id, score: doc.score }),
},
"High scorers fetched"
);❌ THROWING ERRORS
You can throw custom errors anywhere in your logic; the error middleware will catch and format them.
import { NotFoundError } from "express-unified-response";
app.get("/users/:id", async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) throw new NotFoundError("User not found");
res.success(user);
});🧱 Available Error Classes
✨ Automatic Mongoose & JWT Mapping
You don't always have to throw these manually. The middleware automatically detects and converts:
- Mongoose:
ValidationError,CastError, andDuplicateKey (11000)are converted to their respective 400 classes. - JWT:
TokenExpiredErrorandJsonWebTokenErrorare mapped to 401. - Multer: File size limits and unexpected fields are mapped to
FileUploadError.
🔐 Utility: Async Handler (No Try/Catch)
The toolkit provides a asyncHandler utility to catch async errors without try-catch blocks.
import { asyncHandler } from "express-unified-response";
app.get(
"/users",
asyncHandler(async (req, res) => {
const users = await User.find();
res.list(users);
})
);⚙️ Full Configuration
const config = {
keys: {
successKey: "success",
dataKey: "data",
metaKey: "meta",
messageKey: "msg",
errorKey: "err",
},
pagination: {
defaults: {
page: 1,
limit: 10,
maxLimit: 50, // safe limit for paginated lists, limit can never exceed this
},
labels: {
nextPage: "next",
prevPage: "prev",
totalDocs: "totalItems",
docs: "items",
totalPages: "totalPages",
limit: "perPage",
page: "currentPage",
hasPrevPage: "hasPrev",
hasNextPage: "hasNext",
},
},
restDefaults: {
deleteReturnsNoContent: true, // true for 204, false for 200
updateReturnsBody: true, // true for 200, false for 204
nonPaginatedMaxItems: 1000, // safe limit for non-paginated lists
},
logger: {
onSuccess: (req, status, duration) =>
console.log(`✔ ${req.method} ${status} (${duration}ms)`),
onError: (req, err, status) => console.error(`✖ ${err.code} [${status}]`),
},
routeNotFound: true,
error: {
exposeStack: `${process.env.NODE_ENV}` !== "production",
exposeErrorName: false,
defaultErrorMessage: "An unexpected error occurred",
},
};
app.use(createResponseMiddleware(config));
app.use(createErrorMiddleware(config));
Note:Pass the same config tocreateErrorMiddleware(config). to make them consistent.
📤 Response Examples
Success List (Paginated)
{
"success": true,
"data": [{ "id": "123", "score": 90 }],
"meta": {
"currentPage": 1,
"totalItems": 1,
"totalPages": 1,
"hasNextPage": false
}
}Error
{
"success": false,
"msg": "Validation failed",
"err": {
"code": "VALIDATION_ERROR",
"details": [{ "field": "email", "message": "Required" }]
}
}📜 License
MIT License
