Express Request Timeout Middleware

raw JSON →
0.6.1 verified Thu Apr 23 auth: no javascript

This package, `timeout-middleware` (current version 0.6.1), provides an Express.js middleware designed to gracefully handle slow requests by enforcing a response timeout. It intercepts `res.status`, `res.sendStatus`, and `res.send` methods, checking a `res.timedout` flag before allowing further modifications to the response. Upon timeout, if headers have not yet been sent, it automatically sets the response status to 503 (Service Unavailable) and sends a 'Request Timeout' message. This approach differs from alternatives like `connect-timeout` which require manual `haltOnTimedout` checks after every middleware in the chain. The library aims for a more 'DRY' (Don't Repeat Yourself) implementation by wrapping the core response methods, preventing accidental operations on a timed-out request. As a pre-1.0 version, future API changes are possible, and its release cadence is not explicitly defined in the provided information.

error Unhandled 'error' event
cause The `res` object (an `EventEmitter`) emitted an error, and no listener was attached to handle it, causing the Node.js process to terminate.
fix
Add an error listener to the res object within your middleware or a global Express error handler: res.on('error', (err) => { console.error('Response error:', err); });
error Error: Can't set headers after they are sent.
cause Although `timeout-middleware` attempts to prevent this by checking `res.headersSent`, some asynchronous operations in your route handler might attempt to write to the response after it has been implicitly or explicitly finalized (e.g., by the timeout middleware sending a 503 error) and its headers are already committed.
fix
After any potentially long-running or asynchronous operation, always check if (res.timedout || res.headersSent) return; before attempting to send a response or set headers. This ensures that operations only proceed if the response is still open.
breaking This package is currently at version 0.6.1, indicating it is pre-1.0. The API surface or core behavior may undergo breaking changes in future minor or major releases before a stable 1.0 version is reached.
fix Always pin to exact versions (e.g., `"timeout-middleware": "0.6.1"`) and review release notes carefully when upgrading to avoid unexpected behavior.
gotcha The middleware achieves its 'DRY' approach by wrapping and overriding `res.status`, `res.sendStatus`, and `res.send`. If other middleware or application code attempts to directly access or cache the *original* `res` methods after `timeout-middleware` has been applied, it might bypass the timeout check, leading to unexpected behavior or attempts to write to a timed-out response.
fix Ensure that any custom middleware or route handlers rely on the wrapped `res` methods and do not store references to the original `res` functions before `timeout-middleware` is applied. Place `timeout-middleware` high in your middleware chain.
gotcha As `http.ServerResponse` (which Express `res` extends) is an EventEmitter, failure to add a listener for the `error` event can cause the Node.js process to crash if an error occurs during the response lifecycle. The README explicitly mentions this.
fix Always include `res.on('error', err => { console.error(err); /* handle gracefully */ });` or implement a general Express error-handling middleware (`app.use((err, req, res, next) => { ... });`) to catch and manage errors gracefully without crashing the application.
npm install timeout-middleware
yarn add timeout-middleware
pnpm add timeout-middleware

Demonstrates how to apply the `timeout-middleware` globally and shows its effect on both fast and slow routes, preventing responses from being sent after a timeout. Includes basic error handling.

import express from 'express';
import timeout from 'timeout-middleware';
// In a real application, you'd install and use these
// For demonstration, we'll mock them or simply omit them if not critical to timeout behavior
// import bodyParser from 'body-parser';
// import cookieParser from 'cookie-parser';

const app = express();

// Apply the timeout middleware globally with a 2-second timeout
app.use(timeout(2000)); 

// Simulate body-parser and cookie-parser if needed (or install them)
// app.use(bodyParser.json());
// app.use(cookieParser());

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.get('/slow', (req, res) => {
  // Simulate a slow operation that exceeds the timeout
  setTimeout(() => {
    // This will not send 'Too late!' because the timeout middleware intercepted res.send
    // and marked res.timedout = true, returning res instead of calling the original send.
    res.send('Too late! This should not be sent.'); 
  }, 5000); // 5 seconds, which is longer than the 2-second timeout
});

app.get('/fast', (req, res) => {
  setTimeout(() => {
    res.status(200).send('This was fast enough.');
  }, 500); // 0.5 seconds, which is faster than the 2-second timeout
});

// It's crucial to handle the 'error' event on the response object
// to prevent Node.js from crashing if an unhandled error occurs.
app.use((err, req, res, next) => {
  console.error(err);
  if (res.headersSent) {
    return next(err); // Delegate to default Express error handler if headers were already sent
  }
  res.status(500).send('Something broke!');
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}`);
  console.log('Try accessing /slow and /fast routes.');
});