easy-mcp-server
Version:
AI-era Express replacement with zero-config MCP integration - Build AI-ready APIs in 30 seconds
359 lines (323 loc) • 11.6 kB
JavaScript
/**
* APIResponseUtils - Standardized API Response Handling
*
* Provides consistent response formatting for all API endpoints including:
* - Standardized error response formatting
* - Success response standardization
* - Validation error handling
* - Authentication and authorization error responses
* - Request validation utilities
*
* @author EasyNet World
* @version 1.0.0
*/
class APIResponseUtils {
/**
* Send a standardized success response
* @param {Object} res - Express response object
* @param {Object} data - Response data
* @param {string} message - Success message (optional)
* @param {number} statusCode - HTTP status code (default: 200)
*/
static sendSuccessResponse(res, data = null, message = 'Success', statusCode = 200) {
const response = {
success: true,
message,
timestamp: new Date().toISOString(),
...(data && { data })
};
return res.status(statusCode).json(response);
}
/**
* Send a standardized error response
* @param {Object} res - Express response object
* @param {string} message - Error message
* @param {number} statusCode - HTTP status code (default: 500)
* @param {Object} details - Additional error details (optional)
* @param {string} errorCode - Custom error code (optional)
*/
static sendErrorResponse(res, message, statusCode = 500, details = null, errorCode = null) {
const response = {
success: false,
error: message,
timestamp: new Date().toISOString(),
...(errorCode && { errorCode }),
...(details && { details })
};
return res.status(statusCode).json(response);
}
/**
* Send validation error response
* @param {Object} res - Express response object
* @param {Array|Object} errors - Validation errors
* @param {string} message - Custom message (optional)
*/
static sendValidationErrorResponse(res, errors, message = 'Validation failed') {
const response = {
success: false,
error: message,
timestamp: new Date().toISOString(),
validationErrors: Array.isArray(errors) ? errors : [errors]
};
return res.status(400).json(response);
}
/**
* Send not found error response
* @param {Object} res - Express response object
* @param {string} resource - Resource name (e.g., 'User', 'Product')
* @param {string} message - Custom message (optional)
*/
static sendNotFoundResponse(res, resource, message = null) {
const errorMessage = message || `${resource} not found`;
return this.sendErrorResponse(res, errorMessage, 404, null, 'NOT_FOUND');
}
/**
* Send unauthorized error response
* @param {Object} res - Express response object
* @param {string} message - Custom message (optional)
*/
static sendUnauthorizedResponse(res, message = 'Unauthorized access') {
return this.sendErrorResponse(res, message, 401, null, 'UNAUTHORIZED');
}
/**
* Send forbidden error response
* @param {Object} res - Express response object
* @param {string} message - Custom message (optional)
*/
static sendForbiddenResponse(res, message = 'Access forbidden') {
return this.sendErrorResponse(res, message, 403, null, 'FORBIDDEN');
}
/**
* Send rate limit error response
* @param {Object} res - Express response object
* @param {string} message - Custom message (optional)
* @param {number} retryAfter - Retry after seconds (optional)
*/
static sendRateLimitResponse(res, message = 'Rate limit exceeded', retryAfter = null) {
const response = {
success: false,
error: message,
timestamp: new Date().toISOString(),
errorCode: 'RATE_LIMIT_EXCEEDED',
...(retryAfter && { retryAfter })
};
return res.status(429).json(response);
}
/**
* Send internal server error response
* @param {Object} res - Express response object
* @param {string} message - Custom message (optional)
* @param {Object} details - Error details (optional)
*/
static sendInternalErrorResponse(res, message = 'Internal server error', details = null) {
return this.sendErrorResponse(res, message, 500, details, 'INTERNAL_ERROR');
}
/**
* Send bad request error response
* @param {Object} res - Express response object
* @param {string} message - Custom message (optional)
* @param {Object} details - Error details (optional)
*/
static sendBadRequestResponse(res, message = 'Bad request', details = null) {
return this.sendErrorResponse(res, message, 400, details, 'BAD_REQUEST');
}
/**
* Send conflict error response
* @param {Object} res - Express response object
* @param {string} message - Custom message (optional)
* @param {Object} details - Conflict details (optional)
*/
static sendConflictResponse(res, message = 'Resource conflict', details = null) {
return this.sendErrorResponse(res, message, 409, details, 'CONFLICT');
}
/**
* Send service unavailable error response
* @param {Object} res - Express response object
* @param {string} message - Custom message (optional)
* @param {Object} details - Service details (optional)
*/
static sendServiceUnavailableResponse(res, message = 'Service temporarily unavailable', details = null) {
return this.sendErrorResponse(res, message, 503, details, 'SERVICE_UNAVAILABLE');
}
/**
* Validate request body against schema
* @param {Object} body - Request body
* @param {Object} schema - Validation schema
* @returns {Object} Validation result with isValid and errors
*/
static validateRequestBody(body, schema) {
const errors = [];
// Check required fields
if (schema.required) {
for (const field of schema.required) {
if (!(field in body) || body[field] === undefined || body[field] === null) {
errors.push({
field,
message: `${field} is required`,
code: 'REQUIRED_FIELD_MISSING'
});
}
}
}
// Check field types
if (schema.properties) {
for (const [field, fieldSchema] of Object.entries(schema.properties)) {
if (body[field] !== undefined) {
const fieldError = this.validateField(body[field], fieldSchema, field);
if (fieldError) {
errors.push(fieldError);
}
}
}
}
return {
isValid: errors.length === 0,
errors
};
}
/**
* Validate a single field against its schema
* @param {any} value - Field value
* @param {Object} fieldSchema - Field schema
* @param {string} fieldName - Field name
* @returns {Object|null} Validation error or null if valid
* @private
*/
static validateField(value, fieldSchema, fieldName) {
// Type validation
if (fieldSchema.type) {
const expectedType = fieldSchema.type;
const actualType = Array.isArray(value) ? 'array' : typeof value;
if (actualType !== expectedType) {
return {
field: fieldName,
message: `${fieldName} must be of type ${expectedType}`,
code: 'INVALID_TYPE',
expected: expectedType,
actual: actualType
};
}
}
// String length validation
if (fieldSchema.type === 'string') {
if (fieldSchema.minLength && value.length < fieldSchema.minLength) {
return {
field: fieldName,
message: `${fieldName} must be at least ${fieldSchema.minLength} characters long`,
code: 'MIN_LENGTH_VIOLATION',
minLength: fieldSchema.minLength,
actualLength: value.length
};
}
if (fieldSchema.maxLength && value.length > fieldSchema.maxLength) {
return {
field: fieldName,
message: `${fieldName} must be no more than ${fieldSchema.maxLength} characters long`,
code: 'MAX_LENGTH_VIOLATION',
maxLength: fieldSchema.maxLength,
actualLength: value.length
};
}
}
// Number range validation
if (fieldSchema.type === 'number' || fieldSchema.type === 'integer') {
if (fieldSchema.minimum !== undefined && value < fieldSchema.minimum) {
return {
field: fieldName,
message: `${fieldName} must be at least ${fieldSchema.minimum}`,
code: 'MIN_VALUE_VIOLATION',
minimum: fieldSchema.minimum,
actual: value
};
}
if (fieldSchema.maximum !== undefined && value > fieldSchema.maximum) {
return {
field: fieldName,
message: `${fieldName} must be no more than ${fieldSchema.maximum}`,
code: 'MAX_VALUE_VIOLATION',
maximum: fieldSchema.maximum,
actual: value
};
}
}
// Enum validation
if (fieldSchema.enum && !fieldSchema.enum.includes(value)) {
return {
field: fieldName,
message: `${fieldName} must be one of: ${fieldSchema.enum.join(', ')}`,
code: 'INVALID_ENUM_VALUE',
allowedValues: fieldSchema.enum,
actual: value
};
}
return null;
}
/**
* Create a standardized pagination response
* @param {Object} res - Express response object
* @param {Array} data - Array of data items
* @param {Object} pagination - Pagination info
* @param {string} message - Success message (optional)
*/
static sendPaginatedResponse(res, data, pagination, message = 'Success') {
const response = {
success: true,
message,
timestamp: new Date().toISOString(),
data,
pagination: {
page: pagination.page || 1,
limit: pagination.limit || 10,
total: pagination.total || data.length,
totalPages: Math.ceil((pagination.total || data.length) / (pagination.limit || 10)),
hasNext: pagination.hasNext || false,
hasPrev: pagination.hasPrev || false
}
};
return res.status(200).json(response);
}
/**
* Create a standardized list response
* @param {Object} res - Express response object
* @param {Array} data - Array of data items
* @param {string} message - Success message (optional)
* @param {number} statusCode - HTTP status code (default: 200)
*/
static sendListResponse(res, data, message = 'Success', statusCode = 200) {
const response = {
success: true,
message,
timestamp: new Date().toISOString(),
data,
count: data.length
};
return res.status(statusCode).json(response);
}
/**
* Create a standardized created response
* @param {Object} res - Express response object
* @param {Object} data - Created resource data
* @param {string} message - Success message (optional)
*/
static sendCreatedResponse(res, data, message = 'Resource created successfully') {
return this.sendSuccessResponse(res, data, message, 201);
}
/**
* Create a standardized updated response
* @param {Object} res - Express response object
* @param {Object} data - Updated resource data
* @param {string} message - Success message (optional)
*/
static sendUpdatedResponse(res, data, message = 'Resource updated successfully') {
return this.sendSuccessResponse(res, data, message, 200);
}
/**
* Create a standardized deleted response
* @param {Object} res - Express response object
* @param {string} message - Success message (optional)
*/
static sendDeletedResponse(res, message = 'Resource deleted successfully') {
return this.sendSuccessResponse(res, null, message, 200);
}
}
module.exports = APIResponseUtils;