Node.js Express Error Handling
Express 5 observes rejected promises returned by async route handlers. Put an error handler after routes and send a bounded public response instead of a stack trace.
How it works
Known domain rejections and unexpected internal failures should have separate handling. In this fixture an intentional async failure proves that the framework reaches the four-argument error middleware. The example does not install a process-level handler that keeps running after an unknown fatal failure. If headers were already sent, an application must delegate or close appropriately rather than attempting a second complete response.
Run the code
Run this example locally with Node.js. The exact sources are reviewed on Node 22.15.1 and Node 24.22.1 unless a specialised lab states otherwise. Save the complete source as express-errors.mjs in a separate empty exercises folder and run node express-errors.mjs. There is no Node.js browser runner on Bhylu; the plain JavaScript playground does not provide these filesystem, server or process APIs. In that separate project, install the reviewed packages with npm install --save-exact express@5.1.0. These are teaching dependencies, not changes to the Bhylu website package tree.
Download the complete source. Ordinary Node scripts need no Apache, PHP or MySQL service. File examples own their temporary folders; network fixtures use loopback and available temporary ports.
Example
import express from 'express';
import http from 'node:http';
import { once } from 'node:events';
const app = express();
app.get('/broken', async () => {
throw new Error('Private diagnostic');
});
app.use((error, request, response, next) => {
if (response.headersSent) return next(error);
response.status(500).json({ error: 'INTERNAL' });
});
const server = http.createServer(app);
server.listen(0, '127.0.0.1');
await once(server, 'listening');
const base = 'http://127.0.0.1:' + server.address().port;
try {
const response = await fetch(base + '/broken');
console.log(response.status, JSON.stringify(await response.json()));
} finally {
server.closeAllConnections();
await new Promise((resolve, reject) =>
server.close((error) => (error ? reject(error) : resolve())),
);
}
Expected result
500 {"error":"INTERNAL"}The actual local request receives the intended internal-failure status without the private message. Safe server-side diagnostics remain necessary in a real service; removing details from the public body does not mean suppressing every operational record.
Use the Node.js API reference for the precise version contract.