Connect/Express Request Timeout Middleware
connect-timeout is a middleware for Connect and Express applications designed to terminate long-running HTTP requests after a specified duration. The current stable version is 1.9.1. It provides functionality to emit a 'timeout' event on the request object and, optionally, forward a 503 Service Unavailable error to the next middleware. While it flags timed-out requests, it's crucial to understand that it does not inherently stop the underlying server-side processing of the request; developers must explicitly check `req.timedout` to halt further execution and manage resource consumption. Its primary differentiation is its tight integration with the Connect/Express middleware pattern, allowing for flexible placement in the middleware chain, though it warns against top-level usage without precautions. Release cadence appears moderate, with dependency updates and minor fixes.
Common errors
-
Error: Can't set headers after they are sent to the client.
cause The timeout middleware sent a 503 response, but the original request handler or a subsequent middleware also tried to send a response.fixEnsure that all middleware and route handlers check `req.timedout` before attempting to send a response. Implement a `haltOnTimedout` middleware immediately after any potentially slow middleware. -
TypeError: app.use() requires a middleware function but got a undefined
cause The `timeout` function was called without an argument or with an invalid time string, leading it to return `undefined` instead of a middleware function.fixProvide a valid time string (e.g., `'5s'`, `'1000ms'`) or a number (in milliseconds) as the first argument to `timeout()`: `app.use(timeout('5s'))`. -
ConnectTimeoutError: Service Unavailable
cause This is the error object passed to `next()` when a request times out and the `respond` option is `true` (which is the default). It signifies that the `connect-timeout` middleware has detected a timeout.fixImplement an error-handling middleware (`app.use((err, req, res, next) => { ... })`) that specifically checks for `err.timeout === true` and `err.status === 503` to provide a custom response or log the event.
Warnings
- gotcha Using `connect-timeout` as a top-level middleware (`app.use(timeout('5s'))`) without `haltOnTimedout` or similar checks can lead to resource leaks. The middleware will emit a 'timeout' event and potentially send a 503 response, but the underlying request processing (e.g., database queries, heavy computations) will continue in Node.js, consuming CPU and memory resources.
- gotcha The `timeout` event is emitted on the `req` object, not `res`. While `respond: true` (the default) will pass a 503 error to `next()`, explicit handling of the 'timeout' event on `req` might be necessary for specific logic or custom responses outside the standard error handling chain.
- deprecated Older versions of `connect-timeout` (prior to v1.7.0) used to override socket destroy methods, which could lead to memory leaks on keep-alive connections. This was fixed by switching to `on-finished`.
Install
-
npm install connect-timeout -
yarn add connect-timeout -
pnpm add connect-timeout
Imports
- timeout
import { timeout } from 'connect-timeout'import timeout from 'connect-timeout'
- timeout factory function
const timeout = require('connect-timeout') - Middleware usage
app.use(timeout('5s'))
Quickstart
import express from 'express';
import timeout from 'connect-timeout';
const app = express();
// A function to simulate a long-running async operation
function simulateAsyncTask(durationMs) {
return new Promise(resolve => {
setTimeout(() => {
console.log(`Async task finished after ${durationMs}ms.`);
resolve();
}, durationMs);
});
}
// Helper middleware to check if request has timed out and halt further processing
function haltOnTimedout(req, res, next) {
if (!req.timedout) {
next();
} else {
console.warn(`Request to ${req.originalUrl} timed out.`);
// Optionally send a custom response here, or let the error handler catch it
if (!res.headersSent) {
res.status(503).send('Service Unavailable: Request timed out.');
}
}
}
// Route with a 3-second timeout
app.get('/slow-task', timeout('3s'), haltOnTimedout, async (req, res, next) => {
try {
const randomDelay = Math.random() * 5000 + 1000; // 1 to 6 seconds
console.log(`Starting slow task for ${randomDelay}ms.`);
await simulateAsyncTask(randomDelay);
// Check if the request has already timed out before sending a response
if (req.timedout) {
console.log('Request timed out before sending success response.');
return; // Do not send response if already timed out
}
res.status(200).send('Task completed successfully!');
} catch (error) {
next(error);
}
});
// Error handling middleware for timeout errors
app.use((err, req, res, next) => {
if (err.timeout) {
console.error('Request timeout error caught by error handler:', err.message);
// The `haltOnTimedout` middleware above already handles the response,
// but this ensures any other timeout scenarios are caught.
if (!res.headersSent) {
res.status(503).send('Service Unavailable: Caught by error handler.');
}
} else {
console.error('General error:', err.message);
if (!res.headersSent) {
res.status(500).send('Internal Server Error.');
}
}
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
console.log('Try accessing http://localhost:3000/slow-task');
});