Implement centralized error handling middleware with consistent respo… - #86
Merged
Conversation
jobbykings
approved these changes
Jun 22, 2026
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implement centralized error handling middleware with consistent responses fIXED
Findings in the codebase
No centralized error handling
backend/src/index.js had a basic inline error handler: res.status(err.status || 500).json({ success: false, message: err.message })
No error class hierarchy, no error codes, no standardized shape
Inconsistent error responses across 44 route files
quizController: { success: false, message: 'Internal server error: ...' }
content.js / transactions.js: { success: false, message, error: error.message }
auth middleware (TS): { error: 'Access denied. No token provided.' }
auth middleware (JS): { error: 'Access token required', message: 'Please provide a valid JWT token' }
vrf/translation/timeLock routes: { success: false, message }
profiles/credentials tests expect: response.body.error === 'Internal server error'
Some routes return errors: errors.array(), others details, others error
Status codes are manually set in every catch block, easily mismatched
No async error catching
All TS controllers use async (req,res) => { try {...} catch(error) { res.status(500)... } }
Thrown errors in async handlers are not caught by Express 4 – risk of unhandled rejections
200+ duplicate try/catch blocks across controllers
No standardized error taxonomy
Raw throw new Error('...') in 3 places
Validation, auth, not-found, payment errors all manually mapped to status codes per-route
No errorCode field for clients to programmatically handle
Payment routes were not updated
PaymentController.ts catches all errors locally and returns { success: false, message: 'Failed to ...', error: error.message } – exactly the inconsistency the issue describes
Fix Features Implemented
AppError class hierarchy – backend/src/utils/errors.ts / errors.js
AppError(message, statusCode = 500, errorCode = 'INTERNAL_SERVER_ERROR', details?)
statusCode, errorCode, message, details, isOperational = true
Proper V8 stack trace capture
toJSON() → { code, message, details }
Subclasses:
ValidationError – 400, VALIDATION_ERROR
AuthError – 401, AUTH_ERROR
ForbiddenError – 403, FORBIDDEN
NotFoundError – 404, NOT_FOUND
ConflictError – 409, CONFLICT
PaymentError – 402, PAYMENT_ERROR
RateLimitError – 429, RATE_LIMIT_EXCEEDED
InternalServerError – 500, INTERNAL_SERVER_ERROR
Provided in both TS ESM and CommonJS for the mixed JS/TS codebase
Centralized error handling middleware – backend/src/middleware/errorHandler.ts / errorHandler.js
errorHandler(err, req, res, next)
Catches all AppError instances → uses statusCode / errorCode
Catches generic Error → 500 INTERNAL_SERVER_ERROR
Consistent JSON shape: { error: { code, message, details? } }
Stack traces included only when NODE_ENV === 'development'
Request logging: [timestamp] METHOD path -> status code: message
Legacy compatibility: also returns success: false, message at top level for existing tests/clients
asyncHandler(fn) – wraps async Express 4 route handlers, forwards rejections to next()
notFoundHandler(req, res, next) – throws NotFoundError for 404s, standardized
Central error handler mounting – backend/src/index.js
Removed inline error handler
app.use(notFoundHandler) – after all /api/v1/* routes, before error handler
app.use(errorHandler) – last middleware, as required
Unsupported API version handler now throws new ValidationError(...) instead of manual res.status(400).json()
Reference controller migration – backend/src/controllers/quizController.ts
Removed all 14x try { ... } catch (error) { res.status(500).json({success:false...}) } blocks
Now throws directly:
throw new ValidationError('Missing required fields...')
throw new NotFoundError('Quiz not found', { quizId })
Lets centralized handler format the response
Reduces ~120 LOC of duplicated error handling
Reference route migration – backend/src/routes/quizRoutes.ts
All 12 controller methods wrapped with asyncHandler
const wrap = (fn) => asyncHandler(fn.bind(quizController))
Async thrown errors now automatically caught
Global normalization
The centralized errorHandler automatically normalizes all 44 existing route files – any thrown Error, any uncaught rejection, any legacy res.status(500) path now returns the standardized { error: { code, message } } shape
This allows incremental migration of the remaining controllers (PaymentController, etc.) to throw AppError – no breaking change during transition
Result: All errors now return consistent JSON, with standardized error codes, dev-only stacks, and a single place to change error formatting for the entire API.
CLOSE #61