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 🙏

© 2025 – Pkg Stats / Ryan Hefner

axios-cancel-groups

v1.0.1

Published

Grouped request cancellation for Axios with React hooks and Express middleware - Cancel multiple requests by group key, auto-cancel on route changes, and handle timeouts

Readme

axios-cancel-groups

Grouped request cancellation for Axios with React hooks and Express middleware. Cancel multiple requests by group key, auto-cancel on route changes, and handle timeouts elegantly.

Features

  • 🎯 Grouped Request Cancellation: Bulk cancel Axios requests by group key
  • ⚛️ React Hooks: Context Provider and hooks for automatic request management
  • 🔄 Auto-Cancel on Route Change: Automatically cancel requests when navigating
  • ⏱️ Express Timeout Middleware: Hard timeout limits with automatic cleanup
  • 🔌 Client Disconnect Handling: Auto-cleanup when clients disconnect
  • 📦 TypeScript Support: Full type definitions included
  • 🌲 Tree-shakeable: ESM and CJS exports
  • Well Tested: 91 tests covering all features

Installation

npm install axios-cancel-groups

Peer Dependencies

npm install axios        # Required for Axios features
npm install express      # Required for Express middleware
npm install react        # Required for React hooks

Usage

React - Recommended

Wrap your app with the Context Provider for automatic request management:

// App.tsx - Wrap with Provider
import { RequestCancelProvider } from "axios-cancel-groups";

function App() {
  return (
    <RequestCancelProvider axiosConfig={{ baseURL: "/api" }}>
      <YourApp />
    </RequestCancelProvider>
  );
}

Available Hooks

1. Basic Usage - useAxios and group

import { useAxios, group } from "axios-cancel-groups";

function UserList() {
  const [users, setUsers] = useState([]);
  const api = useAxios();

  useEffect(() => {
    // Group requests together
    api.get("/users", group("users-page"))
      .then(res => setUsers(res.data))
      .catch(err => {
        if (err.code !== 'ERR_CANCELED') {
          console.error(err);
        }
      });
  }, []);

  return <div>...</div>;
}

2. Auto-Cancellation - useGroupCancellation

import { useAxios, group, useGroupCancellation } from "axios-cancel-groups";

function ProductDetails({ productId }) {
  const [product, setProduct] = useState(null);
  const api = useAxios();
  
  // Auto-cancel when productId changes or component unmounts
  useGroupCancellation(`product-${productId}`, [productId]);

  useEffect(() => {
    api.get(`/products/${productId}`, group(`product-${productId}`))
      .then(res => setProduct(res.data))
      .catch(err => console.error(err));
  }, [productId]);

  return <div>...</div>;
}

3. Route Change Cancellation - useRouteCancellation

import { useLocation } from "react-router-dom";
import { useAxios, useRouteCancellation } from "axios-cancel-groups";

function UsersPage() {
  const location = useLocation();
  const api = useAxios();
  const [users, setUsers] = useState([]);
  
  // Auto-cancel when route changes
  const routeGroup = useRouteCancellation(location.pathname);

  useEffect(() => {
    api.get("/users", { group: routeGroup } as any)
      .then(res => setUsers(res.data));
  }, []);

  return <div>...</div>;
}

4. Manual Control - useRequestManager

import { useRequestManager } from "axios-cancel-groups";

function Dashboard() {
  const { api, requestManager } = useRequestManager();
  const [stats, setStats] = useState(null);

  const loadDashboard = () => {
    api.get("/stats", { group: "dashboard" } as any)
      .then(res => setStats(res.data));
  };

  const handleLogout = () => {
    // Cancel all pending requests
    requestManager.cancelAll();
  };

  const refreshDashboard = () => {
    // Cancel dashboard requests and reload
    requestManager.cancelGroup("dashboard");
    loadDashboard();
  };

  return (
    <div>
      <button onClick={refreshDashboard}>Refresh</button>
      <button onClick={handleLogout}>Logout</button>
      
      {/* Display active request count */}
      <div>
        Active Requests: {requestManager.getTotalActiveRequests()}
      </div>
    </div>
  );
}

5. Cleanup Hook - useRequestCleanup

import { useRequestCleanup } from "axios-cancel-groups";

function PageWrapper({ children }) {
  // Cancel ALL requests when this component unmounts
  useRequestCleanup();
  
  return <div>{children}</div>;
}

Axios - Frontend (Vanilla JS)

Create an Axios instance with request grouping support:

import { createAxiosWithRequestGroups, group } from "axios-cancel-groups";

// Create API instance with request manager
const { api, requestManager } = createAxiosWithRequestGroups({
  axiosConfig: {
    baseURL: "/api",
  },
});

// Make grouped requests
async function fetchUserData() {
  // Add requests to "page:/users" group
  const usersPromise = api.get("/users", group("page:/users"));
  const profilePromise = api.get("/profile", group("page:/users"));
  
  const [users, profile] = await Promise.all([usersPromise, profilePromise]);
  return { users, profile };
}

// Cancel all requests in a group (e.g., on route change)
function onRouteChange() {
  requestManager.cancelGroup("page:/users");
}

// Cancel all pending requests
function onLogout() {
  requestManager.cancelAll();
}

// Check active requests
console.log(requestManager.getGroupSize("page:/users")); // 0
console.log(requestManager.getTotalActiveRequests()); // 0

React Router Example

import { useEffect } from "react";
import { useLocation } from "react-router-dom";
import { createAxiosWithRequestGroups } from "axios-cancel-groups";

const { api, requestManager } = createAxiosWithRequestGroups({
  axiosConfig: { baseURL: "/api" },
});

function App() {
  const location = useLocation();
  
  useEffect(() => {
    // Cancel previous page's requests when route changes
    return () => {
      requestManager.cancelGroup(`page:${location.pathname}`);
    };
  }, [location.pathname]);
  
  return <div>...</div>;
}

Vue Router Example

import { watch } from "vue";
import { useRoute } from "vue-router";
import { createAxiosWithRequestGroups } from "axios-cancel-groups";

const { api, requestManager } = createAxiosWithRequestGroups({
  axiosConfig: { baseURL: "/api" },
});

export function useRouteCleanup() {
  const route = useRoute();
  
  watch(
    () => route.path,
    (newPath, oldPath) => {
      if (oldPath) {
        requestManager.cancelGroup(`page:${oldPath}`);
      }
    }
  );
}

Express - Backend

Add automatic request cancellation and timeout handling:

import express from "express";
import { cancelAndTimebox } from "axios-cancel-groups";

const app = express();

// Add middleware with 15-second hard timeout
app.use(cancelAndTimebox({ hardTimeoutMs: 15000 }));

// Use req.cancel.signal in your route handlers
app.get("/heavy", async (req, res) => {
  const signal = req.cancel!.signal;
  
  // Long-running operation
  for (let i = 0; i < 100; i++) {
    // Check if request was cancelled
    if (signal.aborted) {
      console.log("Request cancelled by client");
      return;
    }
    
    await new Promise((resolve) => setTimeout(resolve, 200));
  }
  
  res.json({ ok: true });
});

// Database query example
app.get("/users", async (req, res) => {
  try {
    // Pass abort signal to database query (if supported)
    const users = await db.query("SELECT * FROM users", {
      signal: req.cancel!.signal,
    });
    
    res.json(users);
  } catch (error) {
    if (error.name === "AbortError") {
      console.log("Query cancelled");
      return;
    }
    throw error;
  }
});

// File processing example
app.post("/process-file", async (req, res) => {
  const signal = req.cancel!.signal;
  
  const chunks = [];
  for await (const chunk of fileStream) {
    if (signal.aborted) {
      console.log("File processing cancelled");
      return;
    }
    
    chunks.push(processChunk(chunk));
  }
  
  res.json({ processed: chunks.length });
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

Middleware Options

interface CancelAndTimeboxOptions {
  /**
   * Maximum request duration in milliseconds before sending 504 timeout
   * @default undefined (no timeout)
   */
  hardTimeoutMs?: number;
}

When hardTimeoutMs is exceeded:

  • Request's AbortController is aborted
  • Response status is set to 504 Gateway Timeout
  • Connection is closed
  • All event listeners and timers are cleaned up

API Reference

React Hooks

RequestCancelProvider

Context provider that wraps your app and manages request cancellation.

Props:

  • children: ReactNode - Your app components
  • axiosConfig?: AxiosRequestConfig - Optional Axios configuration

useRequestManager()

Returns the axios instance and request manager.

Returns:

{
  api: AxiosInstance,
  requestManager: RequestManager
}

useAxios()

Returns the axios instance for making requests.

Returns: AxiosInstance

useGroupCancellation(groupKey, dependencies?)

Automatically cancels a group when dependencies change or component unmounts.

Parameters:

  • groupKey: string - The group key to cancel
  • dependencies?: any[] - Optional dependency array (like useEffect)

useRouteCancellation(location)

Cancels requests when the route/location changes.

Parameters:

  • location: string - Current location/route path

Returns: string - Group key for the current route (e.g., "route:/users")

useRequestCleanup()

Cancels all pending requests when the component unmounts.

Axios

createAxiosWithRequestGroups(options?)

Creates an Axios instance with request grouping support.

Parameters:

  • options.axiosConfig (optional): Axios configuration object

Returns:

{
  api: AxiosInstance,
  requestManager: RequestManager
}

group(key: string)

Helper function to assign a group key to a request.

Example:

api.get("/users", group("page:/users"));

RequestManager

Methods:

  • cancelGroup(groupKey: string): Cancel all requests in a group
  • cancelAll(): Cancel all pending requests
  • getGroupSize(groupKey: string): Get number of active requests in a group
  • getTotalActiveRequests(): Get total number of active requests

Express

cancelAndTimebox(options?)

Express middleware for automatic request cancellation and timeout handling.

Parameters:

  • options.hardTimeoutMs (optional): Maximum request duration in milliseconds

Adds to Request:

interface Request {
  cancel?: AbortController;
}

TypeScript

The package includes full TypeScript definitions:

import type {
  GroupableConfig,
  RequestManager,
  CancelAndTimeboxOptions,
} from "axios-cancel-groups";

// Extended AxiosRequestConfig with group key
const config: GroupableConfig = {
  url: "/api/users",
  group: "page:/users",
};

// Express Request automatically includes cancel property
app.get("/route", (req, res) => {
  req.cancel!.signal; // AbortSignal
});

Testing

Run tests with Vitest:

# Run tests once
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

Test Suite Coverage

The comprehensive test suite includes:

Unit Tests

axios.test.ts - Axios request grouping (13 tests)

  • ✅ Axios instance creation with interceptors
  • ✅ AbortController attachment to requests
  • ✅ Group key handling and tracking
  • ✅ Request cleanup on success and error
  • ✅ Group cancellation operations
  • ✅ Request manager operations (cancelAll, getGroupSize, etc.)

express.test.ts - Express middleware (20 tests)

  • ✅ AbortController attachment to requests
  • ✅ Client disconnect detection (aborted, close events)
  • ✅ Hard timeout with 504 response
  • ✅ Event listener cleanup
  • ✅ Multiple cleanup trigger handling
  • ✅ Integration scenarios

edge-cases.test.ts - Edge cases and corner scenarios (34 tests)

  • ✅ Undefined, null, NaN, Infinity handling
  • ✅ Special characters in group keys (unicode, emojis, etc.)
  • ✅ Numeric, boolean, object, and Symbol group keys
  • ✅ Empty strings and whitespace handling
  • ✅ Very large values and timeout edge cases
  • ✅ Multiple instance isolation
  • ✅ Concurrent operations and race conditions
  • ✅ Type safety edge cases

types.test.ts - TypeScript type safety (24 tests)

  • ✅ GroupableConfig type extending AxiosRequestConfig
  • ✅ RequestManager method signatures
  • ✅ Express Request extension types
  • ✅ Type inference and generic types
  • ✅ Exported type accessibility

Total: 91 tests - All passing ✅

Build

npm run build

Outputs ESM and CJS formats to dist/ directory.

Release

This package uses release-it for automated releases.

Publishing a New Version

# Patch release (1.0.0 -> 1.0.1)
npm run release:patch

# Minor release (1.0.0 -> 1.1.0)
npm run release:minor

# Major release (1.0.0 -> 2.0.0)
npm run release:major

# Beta release (1.0.0 -> 1.0.1-beta.0)
npm run release:beta

The release process will:

  1. Run tests
  2. Build the package
  3. Update version in package.json
  4. Generate/update CHANGELOG.md
  5. Create a git tag
  6. Push to GitHub
  7. Publish to npm
  8. Create a GitHub release

Manual Release

For manual releases:

npm run release

This will prompt you to select the version type interactively.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT