Fej - Fetch with middleware support

The main class that provides middleware capabilities for the Fetch API. Supports both legacy v1 singleton pattern and new v2 instance-based approach.

Example

// v2 Instance-based approach (recommended)
import { createFej } from 'fej';

const api = createFej({
retry: { attempts: 3, delay: 1000 },
});

api.use('auth', async (ctx, next) => {
ctx.request.init.headers = new Headers(ctx.request.init.headers);
ctx.request.init.headers.set('Authorization', 'Bearer token');
await next();
});

const response = await api.fej('https://api.example.com/users');

Constructors

Methods

  • Execute a fetch request with middleware applied

    This method applies all registered middleware (both v1 and v2) and executes the fetch request. Middleware are executed in priority order (v2) and registration order (v1 backward compatibility).

    Parameters

    • input: RequestInfo

      The URL or Request object to fetch

    • Optional init: RequestInit

      Optional RequestInit configuration

    Returns Promise<Response>

    Promise that resolves to the Response

    Example

    const api = createFej();
    const response = await api.fej('https://api.example.com/users');
    const data = await response.json();

    Example

    const response = await api.fej('https://api.example.com/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: 'John' }),
    });
  • Set global request initialization options (deprecated)

    This method sets global configuration that applies to all requests made with the singleton. It is deprecated in favor of instance-based configuration.

    Parameters

    • init: RequestInit

      Request initialization options to set globally

    Returns void

    Deprecated

    This method is deprecated and will be removed in a future version. Use instance-based configuration instead:

    const api = createFej({ baseURL: "...", headers: {...} });
    
  • Add synchronous middleware (deprecated)

    This method adds a synchronous middleware function using the legacy v1 API. It is deprecated in favor of the unified use() method which supports both sync and async middleware with better control and naming.

    Parameters

    Returns void

    Deprecated

    This method is deprecated and will be removed in a future version. Use the unified use() method instead:

    api.use("middleware-name", async (ctx, next) => {
    // Modify request
    ctx.request.init.headers = new Headers(ctx.request.init.headers);
    ctx.request.init.headers.set('X-Custom', 'value');
    await next();
    });
  • Add asynchronous middleware (deprecated)

    This method adds an asynchronous middleware function using the legacy v1 API. It is deprecated in favor of the unified use() method which handles both sync and async middleware automatically.

    Parameters

    Returns void

    Deprecated

    This method is deprecated and will be removed in a future version. Use the unified use() method instead (handles both sync and async automatically):

    api.use("middleware-name", async (ctx, next) => {
    // Async operations supported
    const token = await getAuthToken();
    ctx.request.init.headers = new Headers(ctx.request.init.headers);
    ctx.request.init.headers.set('Authorization', `Bearer ${token}`);
    await next();
    });
  • Add named middleware with optional priority

    Register a middleware function that will be executed in the request/response pipeline. Middleware are executed in descending priority order (higher priority runs first). Uses Koa-style onion model where middleware can execute code before and after calling next().

    Parameters

    • name: string

      Unique identifier for the middleware (will replace existing with same name)

    • fn: FejMiddlewareFunction

      Middleware function (Koa-style with ctx and next)

    • priority: number = 0

      Optional priority (default: 0, higher = earlier execution)

    Returns string

    The unique ID of the middleware (for internal tracking)

    Example: Basic middleware

    const api = createFej();
    api.use('custom-header', async (ctx, next) => {
    ctx.request.init.headers = new Headers(ctx.request.init.headers);
    ctx.request.init.headers.set('X-Custom', 'value');
    await next();
    });

    Example: Middleware with before/after logic

    api.use('logger', async (ctx, next) => {
    const start = Date.now();
    console.log(`→ ${ctx.request.url}`);

    await next(); // Execute request and downstream middleware

    const duration = Date.now() - start;
    console.log(`← ${ctx.request.url} (${duration}ms)`);
    });

    Example: Middleware with priority

    // Auth runs first (priority: 100)
    api.use('auth', authMiddleware, 100);
    // Logger runs second (priority: 0, default)
    api.use('logger', loggerMiddleware);
  • Remove middleware by name

    Removes a previously registered middleware from the pipeline.

    Parameters

    • name: string

      The name of the middleware to remove

    Returns boolean

    true if middleware was removed, false if not found

    Example

    api.use('temp', tempMiddleware);
    const removed = api.removeMiddleware('temp'); // true
    const removed2 = api.removeMiddleware('nonexistent'); // false
  • Enable or disable middleware without removing it

    Allows temporarily disabling middleware without removing it from the pipeline. Useful for debugging or conditional middleware execution.

    Parameters

    • name: string

      The name of the middleware

    • enabled: boolean

      Whether to enable (true) or disable (false)

    Returns boolean

    true if middleware was found, false otherwise

    Example

    api.use('debug', debugMiddleware);
    api.toggleMiddleware('debug', false); // Disable debug logging
    // ... do something ...
    api.toggleMiddleware('debug', true); // Re-enable debug logging
  • Get the list of all registered middleware names

    Returns middleware names in the order they will execute (sorted by priority).

    Returns string[]

    Array of middleware names in priority order (highest priority first)

    Example

    api.use('auth', authMiddleware, 100);
    api.use('logger', loggerMiddleware, 0);
    api.use('retry', retryMiddleware, 50);
    const names = api.getMiddlewareNames(); // ['auth', 'retry', 'logger']
  • Check if a middleware exists

    Parameters

    • name: string

      The name of the middleware

    Returns boolean

    true if middleware exists, false otherwise

    Example

    api.use('auth', authMiddleware);
    api.hasMiddleware('auth'); // true
    api.hasMiddleware('nonexistent'); // false
  • Add an error transformation function

    Error transforms allow you to customize error handling globally. Transforms are executed in registration order for all errors.

    Parameters

    Returns void

    Example

    const api = createFej();
    api.addErrorTransform(async (error, ctx) => {
    // Add context to all errors
    const enhancedError = new Error(`[${ctx.request.url}] ${error.message}`);
    enhancedError.stack = error.stack;
    return enhancedError;
    });
  • Remove all error transforms

    Clears all registered error transformation functions.

    Returns void

    Example

    api.clearErrorTransforms();
    
  • Set default retry configuration

    Configures the default retry behavior for the instance. This affects middleware that use retry logic.

    Parameters

    • config: Partial<RetryConfig>

      Partial retry configuration

    Returns void

    Example

    const api = createFej();
    api.setDefaultRetry({
    attempts: 5,
    delay: 2000,
    backoff: 'exponential',
    });
  • Create a new AbortController for request cancellation

    Creates an AbortController instance for cancelling requests. The controller is tracked internally and can be aborted by ID or tags.

    Parameters

    • Optional id: string

      Optional custom identifier for the controller (generated if not provided)

    • Optional tags: string[]

      Optional tags for grouping requests (e.g., ['user-profile', 'high-priority'])

    Returns {
        controller: AbortController;
        requestId: string;
    }

    Object containing the AbortController and the request ID

    • controller: AbortController
    • requestId: string

    Example: Basic usage

    const { controller, requestId } = api.createAbortController();
    const response = await api.fej('https://api.example.com/users', {
    signal: controller.signal,
    });

    Example: With tags for batch cancellation

    const { controller } = api.createAbortController(undefined, ['dashboard', 'user-data']);
    const response = await api.fej('/api/user', { signal: controller.signal });
    // Later: cancel all dashboard requests
    api.abortRequestsByTag('dashboard');
  • Abort a request by ID

    Cancels a specific request using its ID. The request ID is returned when creating an AbortController.

    Parameters

    • id: string

      The ID of the request to abort

    • Optional options: CancellationOptions

      Optional cancellation options (e.g., custom reason)

    Returns boolean

    true if request was aborted, false if not found

    Example

    const { controller, requestId } = api.createAbortController();
    const fetchPromise = api.fej('/api/data', { signal: controller.signal });

    // Cancel after 5 seconds
    setTimeout(() => {
    const aborted = api.abortRequest(requestId, { reason: 'Timeout' });
    console.log(aborted); // true
    }, 5000);
  • Abort all requests with a specific tag

    Cancels all requests that were tagged with the specified tag. Useful for cancelling groups of related requests (e.g., all dashboard requests).

    Parameters

    • tag: string

      The tag to match

    • Optional options: CancellationOptions

      Optional cancellation options (e.g., custom reason)

    Returns number

    Number of requests aborted

    Example

    // Tag requests with 'dashboard'
    const { controller: c1 } = api.createAbortController(undefined, ['dashboard']);
    const { controller: c2 } = api.createAbortController(undefined, ['dashboard']);

    const p1 = api.fej('/api/users', { signal: c1.signal });
    const p2 = api.fej('/api/stats', { signal: c2.signal });

    // Cancel all dashboard requests
    const count = api.abortRequestsByTag('dashboard'); // returns 2
  • Abort all pending requests

    Cancels all currently pending requests tracked by this Fej instance. Use with caution as this will cancel ALL requests.

    Parameters

    Returns void

    Example

    // User navigates away - cancel all pending requests
    window.addEventListener('beforeunload', () => {
    api.abortAllRequests({ reason: 'Navigation' });
    });
  • Get all pending request IDs

    Returns an array of all request IDs that are currently pending.

    Returns string[]

    Array of pending request IDs

    Example

    const pending = api.getPendingRequests();
    console.log(`${pending.length} requests in flight`);
  • Get all requests with a specific tag

    Returns request IDs for all requests tagged with the specified tag.

    Parameters

    • tag: string

      The tag to match

    Returns string[]

    Array of request IDs with this tag

    Example

    const dashboardRequests = api.getRequestsByTag('dashboard');
    console.log(`Dashboard has ${dashboardRequests.length} pending requests`);
  • Check if a request is pending

    Checks whether a specific request ID is still pending.

    Parameters

    • id: string

      The request ID

    Returns boolean

    true if pending, false otherwise

    Example

    const { requestId } = api.createAbortController();
    const pending = api.isRequestPending(requestId); // true
    api.abortRequest(requestId);
    const stillPending = api.isRequestPending(requestId); // false