CodingNic

Error Handling

Exercises

Error Handling 30 min read

Exercises

Objectives

By the end of this lesson, you should be able to:

  • Build a complete error-handling setup from scratch: custom errors, try/catch, and a global handler
  • Correctly distinguish operational errors from unexpected bugs in a real app
  • Confirm Express 5’s automatic async error catching works alongside explicit next(err) calls

⚠️ A note on verification: every command and output in this lesson was actually run with Express 5.

Exercise: A Task API With Complete Error Handling

a) Custom error classes. Build an AppError base class (extending Error, carrying statusCode and isOperational), and two subclasses: NotFoundError (404) and ValidationError (400).

b) An async lookup that can fail. Using this seed data:

javascript
let tasks = [{ id: 1, title: 'Write exercises' }];
let nextId = 2;

Write findTaskById(id), returning a Promise that resolves with the matching task or rejects with a NotFoundError if none exists (a setTimeout-wrapped promise, simulating a real async lookup, is fine).

c) GET /tasks/:id with try/catch. Write an async route handler that awaits findTaskById, wrapped in try/catch, calling next(err) on failure.

bash
curl -w " [%{http_code}]" http://localhost:4412/tasks/1
text
{"id":1,"title":"Write exercises"} [200]
bash
curl -w " [%{http_code}]" http://localhost:4412/tasks/99
text
{"error":"NotFoundError","message":"Task not found"} [404]

d) POST /tasks with validation. Reject a missing title with next(new ValidationError('title is required')), returning 400, otherwise create the task and return 201.

bash
curl -w " [%{http_code}]" -X POST http://localhost:4412/tasks -H "Content-Type: application/json" -d '{}'
text
{"error":"ValidationError","message":"title is required"} [400]

e) The same lookup, with no try/catch. Add a second route, GET /tasks-async-fail/:id, awaiting findTaskById with no try/catch at all, relying entirely on Express 5’s automatic catching.

bash
curl -w " [%{http_code}]" http://localhost:4412/tasks-async-fail/99
text
{"error":"NotFoundError","message":"Task not found"} [404]

Confirm this produces the same response as part (c), even without explicit try/catch.

f) An unexpected bug. Add a route that triggers a genuine, unplanned error (reading a property off null, for example), not an AppError at all.

bash
curl -w " [%{http_code}]" http://localhost:4412/crash
text
{"error":"InternalServerError","message":"Something went wrong"} [500]

g) 404 and the global handler. Add a catch-all 404 handler for unmatched routes, and a single global error handler (four parameters, registered last) that checks err.isOperational, returning the specific error for operational errors, and a generic, safe message (logging the real error server-side) for anything else.

bash
curl -w " [%{http_code}]" http://localhost:4412/nowhere
text
{"error":"Not Found"} [404]

h) Test everything. Use curl to test every route above, confirming each returns the correct status code and body, including both error paths (operational and unexpected) and the two async routes (with and without explicit try/catch).

Recap

This module covered handling errors correctly across an entire Express app: catching synchronous errors with try/catch, Express 5’s automatic catching of rejected promises in async handlers (and the fire-and-forget mistake that defeats it), reporting errors explicitly with next(err), representing different failures with custom error classes, and a single global error handler that separates safe, expected errors from unexpected bugs that should never leak their details to a client.

Next module: REST API design, the conventions that make an API predictable to use.