Every shop has bugs nobody has found yet. When a route crashes, Express answers 500 Internal Server Error with an HTML page, and the shop's page, which expects JSON, can't read it. In this shop the Gift card (product 8) has no category, so .charAt(0) crashes on null. The Product type even says so, category: string | null, but the course compiles without null checks, so TypeScript lets the line through.
You replace Express's page with an error handler: a function with four parameters, (err: Error, req: Request, res: Response, next: NextFunction), given to app.use below the routes. Express knows it by those four parameters, and calls it with the error in err for every crash, in every route above it. Error is TypeScript's type for errors, and NextFunction is imported from Express, next to Request and Response. Here is one that answers every crash with a line of plain text and the status 503:
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
res.status(503).send("Try again in a minute");
});
A 500 answer should never carry the error's own text: it names your variables, tables and files, gives an attacker a map of your code, and means nothing to a customer. Send a short message instead, and write the details to the log with console.error, where only you read them. Express logged each crash along with its HTML page; your handler takes over both jobs, so the log line is yours to write. In the handler, err.message is the text of the error that crashed the route.
The check sends three requests, closes the database for a fourth, and runs your test on the shop and on two broken copies. It must print:
The shop gets three requests:
GET /products/1 -> 200 {"category": "Kitchen", "name": "Mug"}
GET /products/99 -> 404 {"error": "Product not found"}
GET /products/8 -> 500 {"error": "Something went wrong on our side"}
log: ERROR GET /products/8 failed: Cannot read properties of null (reading 'charAt')
The database closes:
GET /products/1 -> 500 {"error": "Something went wrong on our side"}
log: ERROR GET /products/1 failed: database is not open
Your tests on the shop:
crash answers json ... ok
1 test: OK
Your tests on a copy with Express's HTML error page:
crash answers json ... FAIL
AssertionError: {} != { error: 'Something went wrong on our side' }
1 test: 1 failed, so they caught this bug
Your tests on a copy that sends the error to the visitor:
crash answers json ... FAIL
AssertionError: { error: "Cannot read properties of null (reading 'charAt')" } != { error: 'Something went wrong on our side' }
1 test: 1 failed, so they caught this bug
Your task: below the route, give app.use an error handler, a function with the parameters (err: Error, req: Request, res: Response, next: NextFunction). It logs one line with console.error: the method, a space, the path, the text " failed: " and err.message. Then it answers { error: "Something went wrong on our side" } with the status 500. Last, write the test "crash answers json": get /products/8 and check both the status and the JSON body.
Assignment
Follow the Coding Tutorial to add the error handler and its log line, then a test that checks both the status and the JSON body of a crash.
Hint
The handler works for every route, so it sits on its own below the route, not inside it, and it needs all four typed parameters, even the ones it doesn't use. A test that checks only the status lets Express's HTML page through, because that is a 500 too: compare response.body with the object as well, with assert.deepStrictEqual.
Free plan: 30 tutor messages. PRO is unlimited.
