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
text/typescript
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);
};