UNPKG

node-js-api-response-ts

Version:

Unified API response and error handling for Express.js in TypeScript. This package provides a middleware for consistent API responses and error handling in Express applications, making it easier to manage API responses and errors in a standardized way.

100 lines (84 loc) 3.58 kB
import { Request, Response, NextFunction, ErrorRequestHandler } from 'express'; import os from 'os'; import { ApiError } from './ApiError'; export const errorHandler: ErrorRequestHandler = ( err: any, req: Request, res: Response, next: NextFunction ) => { let statusCode = err.statusCode || 500; let message = err.message || 'Something went wrong. Please try again later.'; const status = err.status ?? false; const stack = err.stack; const name = err.name || 'Error'; const errCode = err.code; // In development mode, log the stack trace for debugging const isProduction = process.env.NODE_ENV === 'production'; const log = `[${os.hostname()}] [${statusCode}] | ${req.method} ${req.originalUrl} - ${name} | ${message}`; console.error(isProduction && stack ? log : `${log} | ${stack}`); const errorResponse = (statusCode: number, message: string) => { const response: any = { status, statusCode, message, }; if (!isProduction) { response.name = err.name; response.errCode = errCode; response.stack = err.stack; } res.status(statusCode).json(response); }; // Check if the error is an instance of ApiError if (err instanceof ApiError) return errorResponse(statusCode, message); // Mongo Duplicate Key if (err.code === 11000) { statusCode = 409; const fields = err.keyValue ? Object.entries(err.keyValue).map(([key, val]) => `${key}: ${val}`) : []; message = `Duplicate entry: ${fields.join(', ')} already exists.`; return errorResponse(statusCode, message); } // Mongoose validation if (err.name === 'ValidationError') { statusCode = 400; message = Object.values(err.errors).map((e: any) => e.message).join(', '); return errorResponse(statusCode, message); } // CastError for invalid ObjectId if (err.name === 'CastError') { return errorResponse(400, `Invalid ${err.path}: ${err.value}`); } // JWT errors if (err.name === 'TokenExpiredError') return errorResponse(401, 'Access token has expired'); if (err.name === 'JsonWebTokenError') return errorResponse(401, 'Invalid access token'); if (err.name === 'NotBeforeError') return errorResponse(401, 'Access token not active yet'); // System Errors const systemErrors: Record<string, [number, string]> = { ENOENT: [404, 'File or resource not found.'], EACCES: [403, 'Permission denied.'], ECONNREFUSED: [503, 'Connection refused.'], ETIMEDOUT: [504, 'Request timed out.'], ECONNRESET: [502, 'Connection was reset.'], }; if (err.code && systemErrors[err.code]) { const [code, msg] = systemErrors[err.code]; return errorResponse(code, `${msg} (${err.code})`); } // JS Errors const jsErrorTypes = [ 'SyntaxError', 'ReferenceError', 'TypeError', 'RangeError', 'URIError', 'EvalError', 'AggregateError', ]; if (jsErrorTypes.includes(name)) { return errorResponse(500, `Unexpected ${name}: ${message}`); } // Axios/Fetch if (err.isAxiosError || /fetch|network/i.test(message)) { return errorResponse(502, `External API/network request failed: ${message}`); } // Default fallback return errorResponse(statusCode, message); };