tezvyn:

Centralized error handling in an Express API

AI-drafted, machine-checkedSource: interviewadvanced
WHAT IT TESTS

designing one error path.

OUTLINE

a final four-arg error middleware, an asyncHandler wrapper to funnel promise rejections via next, a custom error class with statusCode, returning uniform JSON.

WHAT THIS TESTS This assesses whether you can architect a single error path rather than ad-hoc handling, covering both sync and async failures with a consistent client contract.

A GOOD ANSWER COVERS Three pieces work together. First, a custom error class, for example class ApiError extends Error with a statusCode field, so handlers can throw new ApiError(404, 'not found') and the middleware knows the status. Second, an asyncHandler higher-order function that wraps every async route, doing Promise.resolve(fn(...)).catch(next), so rejected promises and thrown async errors reach Express instead of becoming unhandled rejections (in Express 4). Third, one final error-handling middleware registered last with the four-argument signature (err, req, res, next); it reads err.statusCode || 500, logs the error server-side, and returns a consistent JSON body like { error: { message, status } }. Synchronous throws inside handlers are caught by Express automatically; async ones are funneled by the wrapper. In production you log the stack but never send it to the client.

COMMON WRONG ANSWERS Sprinkling try/catch in every route with divergent response shapes. Forgetting the asyncHandler so async errors hang the request. Returning raw error objects or stack traces to clients, leaking internals. Putting the error handler before routes so it never fires. Using three arguments and breaking arity detection.

LIKELY FOLLOW-UPS How do you map known errors (validation, auth) to specific codes? How do you handle 404 for unmatched routes? How does Express 5 change async catching? What do you log versus return?

ONE CONCRETE EXAMPLE const asyncHandler = fn => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next); app.get('/users/:id', asyncHandler(async (req, res) => { const u = await db.find(req.params.id); if (!u) throw new ApiError(404, 'not found'); res.json(u); })); app.use((err, req, res, next) => { res.status(err.statusCode || 500).json({ error: { message: err.message } }); }); Every route now funnels failures, sync or async, into one consistently formatted JSON response.

Read the original → expressjs.com

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.