Why a Frontend Engineer Built an Express Service and How to Fix Its Performance & Errors
A frontend team created a Docker‑based Express API to serve a shared navigation header, then tackled slow responses, UDP metric crashes, uncaught exceptions, Express error handling quirks, and static‑file performance bottlenecks, ultimately achieving stable sub‑second latency.
Background
In Oct 2018 a shared navigation header needed to be served to many sub‑sites. The header is pure HTML/CSS/JS, so a Server‑Side Include (SSI) service was built with Express and deployed as Docker containers.
Initial implementation
Express, morgan and a custom UDP logger were used to send response‑time metrics to a monitoring system.
const express = require('express');
const dgram = require('dgram');
const morgan = require('morgan');
const socket = dgram.createSocket('udp4');
function sendMetric(message) {
const buffer = Buffer.from(message);
socket.send(buffer, 3333, 'log.xxx.com');
}
function metricFormat(tokens, req, res) {
const responseTime = parseFloat(tokens['response-time'](req, res));
return JSON.stringify({
metric: 'xx.xx.xx',
url: tokens.url(req, res),
value: responseTime,
timestamp: Date.now(),
app: 'fe-api'
});
}
const app = express();
app.use(morgan(metricFormat, { stream: { write: sendMetric } }));Root cause of slow responses
The UDP destination log.xxx.com was unstable. When socket.send threw an exception, it was unhandled, crashing the Node process and causing Docker to restart the container. Response times could reach 10 seconds.
Fix: add an error callback to socket.send and log the error.
socket.send(buffer, 3333, 'log.xxx.com', (err) => {
if (err) {
logger.error(err);
}
});Handling uncaught exceptions
A naïve process.on('uncaughtException') handler only logged the error, leaving the process hanging. A robust handler logs the error, stops accepting new requests, waits for ongoing requests, then exits.
process.on('uncaughtException', (error) => {
logger.error('Uncaught Exception thrown', error);
const timeout = 10000;
server.close(() => process.exit(1));
setTimeout(() => process.exit(1), timeout);
});
process.on('unhandledRejection', (reason) => {
logger.error('Uncaught (in promise)', reason);
const timeout = 10000;
server.close(() => process.exit(1));
setTimeout(() => process.exit(1), timeout);
});Express error‑handling middleware
Express requires a four‑parameter error handler ( err, req, res, next). Missing next caused the middleware to swallow the default handler, leading to ERR_HTTP_HEADERS_SENT when the response had already started. The correct pattern checks res.headersSent and either forwards the error or destroys the socket.
app.use(function (err, req, res, next) {
logger.error(err);
if (res.headersSent) {
return req.socket.destroy();
}
return res.status(500).send({ message: 'Server Error' });
});Static‑file middleware performance
The original setup served the entire public folder with serve-static, causing a file‑existence check on every request and adding up to 300 ms latency.
Solution: reorganise static assets under an assets sub‑folder and mount them on dedicated routes, leaving only API routes for dynamic rendering.
const serveStatic = require('serve-static');
const favicon = require('serve-favicon');
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use('/assets', serveStatic(path.join(__dirname, 'public', 'assets')));
app.use('/swagger-ui', serveStatic(path.join(__dirname, 'public', 'swagger-ui')));
app.use('/', indexRouter);
app.use('/topbar', topbarRouter);
app.use('/footer', footerRouter);After deployment most requests completed in under 40 ms with only occasional spikes.
Key takeaways
Always handle errors from asynchronous APIs (e.g., socket.send) to prevent process crashes.
Use process.on('uncaughtException') and process.on('unhandledRejection') to log, gracefully shut down, and let the container orchestrator restart the service.
Express error‑handling middleware must have four arguments and should respect res.headersSent to avoid ERR_HTTP_HEADERS_SENT.
Serve only the necessary static directories and mount them on specific routes to eliminate unnecessary file‑system checks.
For further reference see the Node.js Best Practices repository at https://github.com/i0natan/nodebestpractices.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
