UNPKG

ezy-response

Version:

Simplified and standardized Express.js response handling with clean, one-liner syntax for success, error, custom, and stream-based responses

382 lines (331 loc) 10.2 kB
/** * ezy-response - Simplified Express.js response handling * A clean, one-liner syntax for sending success, error, custom, or stream-based responses * * @author Bittu-the-coder * @version 1.0.0 */ const compression = require('compression'); /** * Enhanced response object with ezy-response methods * @typedef {Object} EzyResponse * @extends {import('express').Response} */ /** * Success response options * @typedef {Object} SuccessOptions * @property {string} [message] - Success message * @property {any} [data] - Response data * @property {Object} [meta] - Additional metadata * @property {number} [statusCode=200] - HTTP status code */ /** * Error response options * @typedef {Object} ErrorOptions * @property {string} [message] - Error message * @property {any} [error] - Error details * @property {string} [code] - Error code * @property {number} [statusCode=500] - HTTP status code * @property {Object} [meta] - Additional metadata */ /** * Custom response options * @typedef {Object} CustomOptions * @property {number} statusCode - HTTP status code * @property {any} data - Response data * @property {Object} [headers] - Additional headers */ /** * Pagination metadata * @typedef {Object} PaginationMeta * @property {number} page - Current page * @property {number} limit - Items per page * @property {number} total - Total items * @property {number} totalPages - Total pages * @property {boolean} hasNext - Has next page * @property {boolean} hasPrev - Has previous page */ /** * Send a standardized success response * @param {SuccessOptions} options - Success response options * @returns {import('express').Response} */ function success(options = {}) { const { message = 'Success', data = null, meta = {}, statusCode = 200 } = options; const response = { success: true, message, ...(data !== null && { data }), ...(Object.keys(meta).length > 0 && { meta }), timestamp: new Date().toISOString() }; return this.status(statusCode).json(response); } /** * Send a standardized error response * @param {ErrorOptions} options - Error response options * @returns {import('express').Response} */ function error(options = {}) { const { message = 'An error occurred', error = null, code = 'INTERNAL_ERROR', statusCode = 500, meta = {} } = options; const response = { success: false, message, code, ...(error !== null && { error }), ...(Object.keys(meta).length > 0 && { meta }), timestamp: new Date().toISOString() }; return this.status(statusCode).json(response); } /** * Send a custom response * @param {CustomOptions} options - Custom response options * @returns {import('express').Response} */ function custom(options) { const { statusCode, data, headers = {} } = options; // Set additional headers if provided Object.keys(headers).forEach(key => { this.set(key, headers[key]); }); return this.status(statusCode).json(data); } /** * Send a paginated success response * @param {Object} options - Pagination options * @param {any} options.data - Response data * @param {PaginationMeta} options.pagination - Pagination metadata * @param {string} [options.message] - Success message * @returns {import('express').Response} */ function paginated(options) { const { data, pagination, message = 'Data retrieved successfully' } = options; const response = { success: true, message, data, pagination, timestamp: new Date().toISOString() }; return this.status(200).json(response); } /** * Send a no content response * @param {string} [message] - Optional message * @returns {import('express').Response} */ function noContent(message) { if (message) { return this.status(200).json({ message, timestamp: new Date().toISOString() }); } return this.status(204).send(); } /** * Send a created response * @param {Object} options - Created response options * @param {any} options.data - Created resource data * @param {string} [options.message] - Success message * @param {string} [options.location] - Location header for the created resource * @returns {import('express').Response} */ function created(options) { const { data, message = 'Resource created successfully', location } = options; if (location) { this.set('Location', location); } const response = { success: true, message, data, timestamp: new Date().toISOString() }; return this.status(201).json(response); } /** * Send a validation error response * @param {Object} options - Validation error options * @param {Array} options.errors - Array of validation errors * @param {string} [options.message] - Error message * @returns {import('express').Response} */ function validationError(options) { const { errors, message = 'Validation failed' } = options; const response = { success: false, message, code: 'VALIDATION_ERROR', errors, timestamp: new Date().toISOString() }; return this.status(422).json(response); } /** * Send an unauthorized response * @param {string} [message] - Error message * @returns {import('express').Response} */ function unauthorized(message = 'Unauthorized access') { const response = { success: false, message, code: 'UNAUTHORIZED', timestamp: new Date().toISOString() }; return this.status(401).json(response); } /** * Send a forbidden response * @param {string} [message] - Error message * @returns {import('express').Response} */ function forbidden(message = 'Access forbidden') { const response = { success: false, message, code: 'FORBIDDEN', timestamp: new Date().toISOString() }; return this.status(403).json(response); } /** * Send a not found response * @param {string} [message] - Error message * @returns {import('express').Response} */ function notFound(message = 'Resource not found') { const response = { success: false, message, code: 'NOT_FOUND', timestamp: new Date().toISOString() }; return this.status(404).json(response); } /** * Send a conflict response * @param {string} [message] - Error message * @returns {import('express').Response} */ function conflict(message = 'Resource conflict') { const response = { success: false, message, code: 'CONFLICT', timestamp: new Date().toISOString() }; return this.status(409).json(response); } /** * Send a rate limit exceeded response * @param {Object} [options] - Rate limit options * @param {string} [options.message] - Error message * @param {number} [options.retryAfter] - Retry after seconds * @returns {import('express').Response} */ function rateLimitExceeded(options = {}) { const { message = 'Rate limit exceeded', retryAfter = 60 } = options; if (retryAfter) { this.set('Retry-After', retryAfter.toString()); } const response = { success: false, message, code: 'RATE_LIMIT_EXCEEDED', retryAfter, timestamp: new Date().toISOString() }; return this.status(429).json(response); } /** * Stream a file as response * @param {Object} options - Stream options * @param {string} options.filePath - Path to the file * @param {string} [options.filename] - Download filename * @param {string} [options.mimeType] - MIME type * @param {boolean} [options.inline=false] - Whether to display inline or as attachment * @returns {import('express').Response} */ function streamFile(options) { const { filePath, filename, mimeType, inline = false } = options; if (filename) { const disposition = inline ? 'inline' : 'attachment'; this.set('Content-Disposition', `${disposition}; filename="${filename}"`); } if (mimeType) { this.set('Content-Type', mimeType); } return this.sendFile(filePath); } /** * Send a server error response * @param {string} [message] - Error message * @param {any} [error] - Error details (only included in development) * @returns {import('express').Response} */ function serverError(message = 'Internal server error', error = null) { const response = { success: false, message, code: 'INTERNAL_SERVER_ERROR', timestamp: new Date().toISOString() }; // Only include error details in development environment if (process.env.NODE_ENV === 'development' && error) { response.error = error; } return this.status(500).json(response); } /** * Middleware to add ezy-response methods to Express response object * @param {Object} [options] - Middleware options * @param {boolean} [options.enableCompression=false] - Enable gzip compression * @returns {Function} Express middleware */ function ezyResponse(options = {}) { const { enableCompression = false } = options; return (req, res, next) => { // Add all ezy-response methods to the response object res.success = success.bind(res); res.error = error.bind(res); res.custom = custom.bind(res); res.paginated = paginated.bind(res); res.noContent = noContent.bind(res); res.created = created.bind(res); res.validationError = validationError.bind(res); res.unauthorized = unauthorized.bind(res); res.forbidden = forbidden.bind(res); res.notFound = notFound.bind(res); res.conflict = conflict.bind(res); res.rateLimitExceeded = rateLimitExceeded.bind(res); res.streamFile = streamFile.bind(res); res.serverError = serverError.bind(res); // Enable compression if requested if (enableCompression) { return compression()(req, res, next); } next(); }; } // Export the middleware and individual functions module.exports = ezyResponse; module.exports.success = success; module.exports.error = error; module.exports.custom = custom; module.exports.paginated = paginated; module.exports.noContent = noContent; module.exports.created = created; module.exports.validationError = validationError; module.exports.unauthorized = unauthorized; module.exports.forbidden = forbidden; module.exports.notFound = notFound; module.exports.conflict = conflict; module.exports.rateLimitExceeded = rateLimitExceeded; module.exports.streamFile = streamFile; module.exports.serverError = serverError;