npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

http-request-abort

v1.0.0

Published

> Documentation for the `http-request-abort` middleware and helpers. This package provides abort functionality for HTTP requests in Node.js servers.

Readme

http-request-abort

Documentation for the http-request-abort middleware and helpers. This package provides abort functionality for HTTP requests in Node.js servers.


Table of contents


Overview

This package adds a small, safe AbortController-based abort mechanism to incoming HTTP requests. The middleware:

  • attaches an AbortSignal to req.signal (if one is not already present),
  • listens for request/response lifecycle events that should cancel in-flight operations (request close, request error, response finish/close/error),
  • optionally enforces a timeout,
  • optionally emits an 'abort' event on the request,
  • exposes helpers: isAborted, getAbortReason, and raceWithAbort.

It is intentionally conservative: it will not overwrite an existing req.signal.


When to use

  • You have long-running async work per-request (database queries, remote API calls, streams) and want a standard way to cancel that work if the client disconnects, the response finishes, or a timeout occurs.
  • You want consistent abort semantics across Express/Connect-style middleware or raw http servers.
  • You want to centralize logging and abort reason handling.

Installation

npm install http-request-abort

Quick example (Express)

import express from "express";
import abort, {
  HttpRequestAbortError,
  raceWithAbort,
} from "http-request-abort";

const app = express();

// Add middleware early
app.use(abort({ timeout: 5000, logger: console.log }));

app.get("/slow", async (req, res) => {
  try {
    // use the attached signal in any cancellable operation
    const result = await raceWithAbort(fetchSomething(), req.signal, undefined);
    res.json(result);
  } catch (err) {
    if (err instanceof HttpRequestAbortError) {
      // client disconnected or timed out
      res.status(499).end("Client aborted");
      return;
    }
    res.status(500).end("Server error");
  }
});

Quick example (native http server)

import http from "http";
import abort, { getAbortReason, isAborted } from "http-request-abort";

const handler = (req, res) => {
  // emulate express-like next
  const next = (err) => {
    if (err) {
      res.statusCode = 500;
      res.end(String(err.message ?? err));
      return;
    }
    // Handler continues...
    // Use req.signal for cancellable ops
    someAsyncWork(req.signal)
      .then((v) => {
        if (!isAborted(req)) res.end("OK");
        else res.end("Aborted");
      })
      .catch((err) => res.end("err"));
  };

  // apply middleware then continue to handler logic
  abort({ timeout: 2000 })(req, res, next);
};

http.createServer(handler).listen(3000);

Detailed API

Types & interfaces

  • AbortPhase = "initialization" | "request" | "response" Where the abort happened (used by HttpRequestAbortError).

  • AbortContext

    {
      readonly url?: string;
      readonly cause?: string;
      readonly phase: AbortPhase;
      readonly timestamp: number;
    }

    Context object created when aborting (useful for logging).

  • Logger (message: string, context?: Record<string, any>) => void Custom logger function accepted via options.

  • AbortOptions

    {
      readonly failOnRequestClosed?: boolean;
      readonly failOnResponseClosed?: boolean;
      readonly reason?: any;
      readonly timeout?: number;
      readonly logger?: Logger;
      readonly emitAbortEvent?: boolean;
    }

    Options passed to abort() (see Options & defaults).


HttpRequestAbortError

  • Extends Error.

  • Additional properties:

    • code: "HTTP_REQUEST_ABORTED"
    • timestamp: number (when the error instance was created)
    • cause?: string
    • phase?: AbortPhase
  • toJSON() returns an object with { name, message, code, cause, phase, timestamp }.

Usage notes:

  • The middleware uses HttpRequestAbortError when it needs to report a fail-fast abort during initialization (calls next(err) with this error).
  • raceWithAbort also rejects with HttpRequestAbortError when a timeout or AbortSignal abort occurs.

Default export: abort(options?: AbortOptions)

Signature

export default function abort(
  options?: AbortOptions
): (
  req: IncomingMessage,
  res: ServerResponse,
  next: (err?: any) => void
) => void;

Behavior

  • If req.signal already exists, middleware logs that it's skipping (if logger provided) and calls next() immediately — it will not overwrite an existing signal.

  • Fail-fast checks:

    • If failOnRequestClosed is true (default) and the request is already closed (req.destroyed || req.complete), it immediately calls next(new HttpRequestAbortError(...)).
    • If failOnResponseClosed is true (default) and the response is already closed (res.writableEnded || res.closed), it immediately calls next(new HttpRequestAbortError(...)).
  • Otherwise:

    • Constructs a new AbortController, sets req.signal = controller.signal.

    • Registers listeners:

      • req.once("close") => abort
      • req.once("error") => abort with message
      • res.once("finish") => abort (scheduled via setImmediate)
      • res.once("close") => abort
      • res.once("error") => abort with message
      • controller.signal.addEventListener("abort") => abort (covers external aborts of the controller)
    • Optionally sets a timeout that will call abort when expired.

    • Optionally emitAbortEvent: when true and the request is not destroyed, the middleware will req.emit("abort") on abort.

    • The middleware ensures listeners that it added are removed via removeListener when aborting or on cleanup.

    • If next() throws synchronously, the middleware calls doAbort(...) then rethrows the error (preserving original behavior).

Note: the middleware expects next to be an Express/Connect-style continuation function. For raw http usage, you can pass a wrapper next that continues request handling.


Utilities

  • isAborted(req: IncomingMessage): boolean Returns !!req.signal?.aborted.

  • getAbortReason(req: IncomingMessage): any Returns req.signal?.reason (if present) — the value passed to AbortController.abort(reason).

  • raceWithAbort<T>(promise: Promise<T>, signal?: AbortSignal, timeoutMs?: number): Promise<T> Races promise against:

    • signal abort: rejects with HttpRequestAbortError("Operation aborted", reason) where reason = signal.reason ?? "Signal aborted".
    • timeoutMs expiration: rejects with HttpRequestAbortError("Operation timed out", 'Timeout after ${timeoutMs}ms').
    • If neither signal nor timeout is supplied, returns the original promise as-is.

Important: raceWithAbort cleans up listeners/timers after settling and ensures the first to settle wins. If the underlying promise rejects, that rejection is preserved.


Options & defaults

| Option | Type | Default | Description | | ---------------------- | -------------------: | ----------: | -------------------------------------------------------------------------------------------------------------------------------- | | failOnRequestClosed | boolean | true | If true, fail fast (call next with HttpRequestAbortError) when the request is already closed during middleware initialization. | | failOnResponseClosed | boolean | true | If true, fail fast when the response is already closed during middleware initialization. | | reason | any | undefined | If provided, used as the reason when calling controller.abort(reason) (instead of default textual cause). | | timeout | number | 0 | Milliseconds to wait before automatically aborting the request. 0 or falsy disables timeout. | | logger | (msg, ctx) => void | undefined | Logger function called for lifecycle events (safe: errors in logger are caught by the middleware). | | emitAbortEvent | boolean | false | If true, the middleware will emit an 'abort' event on the req when aborting (if req is not destroyed). |


Behavior and lifecycle

  1. Initialization

    • Middleware first checks if req.signal exists: if yes, it skips and calls next().
    • Then it performs fail-fast checks on request/response closed state.
  2. Attachment

    • Creates AbortController, sets req.signal to controller.signal.
    • Adds listeners for request/response events and for controller.signal abort event.
  3. Abort triggers (examples)

    • Client disconnects (req emits close) → abort.
    • Request emits error → abort with error message.
    • Response finish (completed successfully) → abort (scheduled with setImmediate).
    • Response close or error → abort.
    • timeout reached → abort with timeout message.
    • External call to the same controller (ifAccessible) → controller.signal abort event triggers abort logic.
  4. When aborting

    • The middleware calls controller.abort(reason) unless already aborted.
    • Calls logger?.("aborting request", ctx) where ctx is AbortContext.
    • Optionally emits req.emit("abort") if emitAbortEvent is true and req is not destroyed.
    • Cleans up timers and removes only the listeners it added.
  5. Synchronous errors from next()

    • If next() throws synchronously, middleware calls doAbort(...) and then rethrows the error, so upstream still sees the exception.

Events listened & cleanup guarantees

Events registered

  • On req:

    • close (once)
    • error (once)
  • On res:

    • finish (once)
    • close (once)
    • error (once)
  • On controller.signal:

    • abort (listener)

Cleanup

  • The middleware removes only the listeners it added by calling removeListener on each named callback and clears timeout if set. This minimizes interference with other code using the same req/res events.

Edge cases & gotchas

  • Existing req.signal: The middleware does not overwrite an existing req.signal. If your application or another middleware already set req.signal, this middleware will skip (and log if a logger is present).
  • AbortSignal.reason availability: Some Node.js versions expose AbortSignal.reason (when abort is called with a reason). If your Node version does not support signal.reason, getAbortReason may be undefined. Use getAbortReason helper to access the reason (it reads req.signal?.reason).
  • res.finish scheduling: The middleware treats res.finish as a natural completion and triggers abort on the next tick (setImmediate) to avoid interfering with synchronous post-finish tasks.
  • Races at initialization: The middleware checks request/response closed state and also schedules a setImmediate race-check for the response to handle synchronous state changes that happen during setup.
  • Logger safety: Errors thrown inside your provided logger will be swallowed so that logging cannot break request flow.
  • emitAbortEvent caution: Emitting 'abort' on the request may cause other code listening for it to run. Only set emitAbortEvent: true if you want that extra behaviour.
  • Memory leaks: The implementation removes its listeners when aborting or cleaning up. However, if your next() never returns and you never reach abort/finish/close, a timeout can be used to ensure cleanup.

Testing guidance

  • Unit-test the middleware with lightweight mocks that emulate IncomingMessage and ServerResponse (both extend EventEmitter in Node). You only need emit, once, removeListener, and the properties the middleware inspects (url, destroyed, complete, writableEnded, closed).

  • Test the following scenarios:

    • req.signal already present → middleware does nothing
    • req.destroyed or res.writableEnded during init → fail-fast HttpRequestAbortError via next(err)
    • timeout triggered → req.signal.aborted === true and getAbortReason contains the timeout cause
    • req.emit('close') triggers abort and (if emitAbortEvent) req.emit('abort')
    • res.emit('finish') triggers abort (due to setImmediate)
    • next() throws synchronously → middleware aborts then rethrows error
    • raceWithAbort: resolves normally, underlying rejection passes through, timeout causes HttpRequestAbortError, signal.abort() causes HttpRequestAbortError
  • Use Node's node:test or your favorite test runner. Mock AbortController only if your runtime doesn't support it (Node >= 16.7+ includes AbortController).


Compatibility & runtime notes

  • Requires a Node.js runtime with AbortController/AbortSignal (Node 16.7+ has built-in AbortController; newer Node versions add AbortSignal.reason support).
  • The module interacts with req/res objects that have Node http semantics (IncomingMessage and ServerResponse). It was designed to be compatible with Express/Connect and raw Node http servers.
  • When compiling/transpiling TypeScript for use in Node, ensure the module format (ESM vs CommonJS) matches your consuming environment.

Best practices and security considerations

  • Do not rely on emitAbortEvent unless you control listeners — it will trigger any listener on req for 'abort'.
  • Provide a logger for production so you can trace abort reasons and durations; the middleware logs useful context when available.
  • Prefer raceWithAbort when awaiting cancellable operations so resources are released as soon as a client disconnects or a timeout occurs.
  • Avoid long timeouts unless necessary. A conservative request timeout prevents resource exhaustion under heavy load.
  • Handle HttpRequestAbortError gracefully in async handlers — most aborts are normal (client disconnects, timeouts) and should not crash the process. Return appropriate status codes or simply stop work and close resources.

Full TypeScript signatures (reference)

// Types
export type AbortPhase = "initialization" | "request" | "response";

export interface AbortContext {
  readonly url?: string;
  readonly cause?: string;
  readonly phase: AbortPhase;
  readonly timestamp: number;
}

export type Logger = (message: string, context?: Record<string, any>) => void;

export interface AbortOptions {
  readonly failOnRequestClosed?: boolean;
  readonly failOnResponseClosed?: boolean;
  readonly reason?: any;
  readonly timeout?: number;
  readonly logger?: Logger;
  readonly emitAbortEvent?: boolean;
}

// Exports
export class HttpRequestAbortError extends Error {
  public readonly code: "HTTP_REQUEST_ABORTED";
  public readonly timestamp: number;

  constructor(message: string, cause?: string, phase?: AbortPhase);

  toJSON(): {
    name: string;
    message: string;
    code: string;
    cause?: string;
    phase?: AbortPhase;
    timestamp: number;
  };
}

export default function abort(
  options?: AbortOptions
): (
  req: IncomingMessage,
  res: ServerResponse,
  next: (err?: any) => void
) => void;

export const isAborted: (req: IncomingMessage) => boolean;
export const getAbortReason: (req: IncomingMessage) => any;

export async function raceWithAbort<T>(
  promise: Promise<T>,
  signal?: AbortSignal,
  timeoutMs?: number
): Promise<T>;

Example: robust handler pattern

import abort, {
  HttpRequestAbortError,
  raceWithAbort,
} from "http-request-abort";

app.use(abort({ timeout: 10000, logger: (m, c) => console.debug(m, c) }));

app.get("/data", async (req, res) => {
  try {
    const data = await raceWithAbort(fetchFromRemote(), req.signal, undefined);
    res.json(data);
  } catch (err) {
    if (err instanceof HttpRequestAbortError) {
      // abort was triggered by client disconnect or timeout:
      // no need to call res.end if connection is gone — safe fallback:
      if (!res.writableEnded) {
        res.status(499).end("Client aborted");
      }
      return;
    }
    // Handle other errors
    res.status(500).end("Internal error");
  }
});

Contributing

Contributions are welcome! Feel free to open issues or submit pull requests.

License

MIT License. See LICENSE for details.

Author

Made with ❤️ by Rayen Boussayed