Skip to content

Express Integration

Real-world example of using Enhanced AbortController with Express.js for request timeout and cancellation.

Basic Express Example

typescript
import express from 'express';
import {
  EnhancedAbortController,
  EnhancedAbortSignal,
  AbortError,
} from '@nodelibraries/enhanced-abort-controller';

const app = express();

// Middleware to create abort controller for each request
app.use((req, res, next) => {
  const controller = new EnhancedAbortController();

  // Auto-abort after 30 seconds
  controller.abortAfter(30000);

  // Attach controller to request
  (req as any).abortController = controller;

  // Clean up when request finishes
  res.on('close', () => {
    controller.dispose();
  });

  next();
});

// Route handler with abort support
app.get('/api/data', async (req, res) => {
  const controller = (req as any).abortController as EnhancedAbortController;
  const signal = controller.signal;

  try {
    // Simulate async operation with abort support
    const data = await fetchData(signal);
    res.json(data);
  } catch (error) {
    if (error instanceof AbortError) {
      res.status(408).json({ error: 'Request timeout' });
    } else {
      res.status(500).json({ error: 'Internal server error' });
    }
  }
});

async function fetchData(signal: EnhancedAbortSignal) {
  // Check if aborted before starting
  signal.throwIfAborted();

  // Simulate database query with abort support
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      resolve({ data: 'Some data' });
    }, 2000);

    // Register cleanup
    signal.register(() => {
      clearTimeout(timeout);
      reject(new AbortError('Operation aborted'));
    });
  });
}

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

Advanced Express Example with Multiple Operations

typescript
import express from 'express';
import {
  EnhancedAbortController,
  EnhancedAbortSignal,
  AbortError,
  TimeSpan,
} from '@nodelibraries/enhanced-abort-controller';

const app = express();

// Middleware with configurable timeout
app.use((req, res, next) => {
  const timeout = req.query.timeout
    ? parseInt(req.query.timeout as string)
    : 30000;

  const controller = new EnhancedAbortController();
  controller.abortAfter(timeout);

  (req as any).abortController = controller;

  res.on('close', () => {
    controller.dispose();
  });

  next();
});

// Complex route with multiple async operations
app.get('/api/users/:id', async (req, res) => {
  const controller = (req as any).abortController as EnhancedAbortController;
  const signal = controller.signal;

  try {
    // Multiple operations that can be cancelled
    const [user, posts, comments] = await Promise.all([
      fetchUser(req.params.id, signal),
      fetchUserPosts(req.params.id, signal),
      fetchUserComments(req.params.id, signal),
    ]);

    res.json({ user, posts, comments });
  } catch (error) {
    if (error instanceof AbortError) {
      res.status(408).json({
        error: 'Request timeout',
        reason: controller.reason,
      });
    } else {
      res.status(500).json({ error: 'Internal server error' });
    }
  }
});

async function fetchUser(id: string, signal: EnhancedAbortSignal) {
  signal.throwIfAborted();

  // Simulate database query
  await delay(1000, signal);
  return { id, name: 'John Doe' };
}

async function fetchUserPosts(id: string, signal: EnhancedAbortSignal) {
  signal.throwIfAborted();

  // Simulate API call
  await delay(1500, signal);
  return [{ id: 1, title: 'Post 1' }];
}

async function fetchUserComments(id: string, signal: EnhancedAbortSignal) {
  signal.throwIfAborted();

  // Simulate external service call
  await delay(2000, signal);
  return [{ id: 1, text: 'Comment 1' }];
}

function delay(ms: number, signal: EnhancedAbortSignal): Promise<void> {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(resolve, ms);

    signal.register(() => {
      clearTimeout(timeout);
      reject(new AbortError('Operation aborted'));
    });
  });
}

app.listen(3000);

Express with Manual Cancellation

typescript
import express from 'express';
import {
  EnhancedAbortController,
  AbortError,
} from '@nodelibraries/enhanced-abort-controller';

const app = express();
app.use(express.json());

// Store active requests
const activeRequests = new Map<string, EnhancedAbortController>();

app.post('/api/long-running-task', async (req, res) => {
  const taskId = req.body.taskId || `task-${Date.now()}`;
  const controller = new EnhancedAbortController();

  activeRequests.set(taskId, controller);

  try {
    // Long-running operation
    const result = await processLongTask(taskId, controller.signal);
    activeRequests.delete(taskId);
    res.json({ taskId, result, status: 'completed' });
  } catch (error) {
    activeRequests.delete(taskId);
    if (error instanceof AbortError) {
      res.status(499).json({ taskId, status: 'cancelled' });
    } else {
      res.status(500).json({ error: 'Task failed' });
    }
  }
});

// Cancel endpoint
app.post('/api/cancel-task', (req, res) => {
  const { taskId } = req.body;
  const controller = activeRequests.get(taskId);

  if (controller) {
    controller.abort('Cancelled by user');
    activeRequests.delete(taskId);
    res.json({ taskId, status: 'cancelled' });
  } else {
    res.status(404).json({ error: 'Task not found' });
  }
});

async function processLongTask(
  taskId: string,
  signal: EnhancedAbortSignal
): Promise<string> {
  for (let i = 0; i < 100; i++) {
    signal.throwIfAborted();
    await delay(100, signal);
    console.log(`Task ${taskId}: Progress ${i}%`);
  }
  return 'Task completed';
}

function delay(ms: number, signal: EnhancedAbortSignal): Promise<void> {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(resolve, ms);
    signal.register(() => {
      clearTimeout(timeout);
      reject(new AbortError('Operation aborted'));
    });
  });
}

app.listen(3000);

Express with Linked Controllers

typescript
import express from 'express';
import {
  EnhancedAbortController,
  AbortError,
} from '@nodelibraries/enhanced-abort-controller';

const app = express();

app.use((req, res, next) => {
  // User cancellation controller
  const userController = new EnhancedAbortController();

  // Timeout controller (30 seconds)
  const timeoutController = EnhancedAbortController.timeout(30000);

  // Link both - abort if user cancels OR timeout occurs
  const linkedController = EnhancedAbortController.linkSignals(
    userController.signal,
    timeoutController.signal
  );

  (req as any).abortController = linkedController;
  (req as any).userController = userController;

  res.on('close', () => {
    userController.dispose();
    timeoutController.dispose();
  });

  next();
});

app.get('/api/data', async (req, res) => {
  const controller = (req as any).abortController as EnhancedAbortController;

  try {
    const data = await fetchData(controller.signal);
    res.json(data);
  } catch (error) {
    if (error instanceof AbortError) {
      res.status(408).json({ error: 'Request cancelled or timeout' });
    } else {
      res.status(500).json({ error: 'Internal server error' });
    }
  }
});

// Manual cancellation endpoint
app.post('/api/cancel', (req, res) => {
  const userController = (req as any).userController as EnhancedAbortController;

  if (userController && !userController.isAborted) {
    userController.abort('Cancelled by user');
    res.json({ status: 'cancelled' });
  } else {
    res.status(400).json({ error: 'No active request to cancel' });
  }
});

async function fetchData(signal: EnhancedAbortSignal) {
  signal.throwIfAborted();
  await delay(5000, signal);
  return { data: 'Some data' };
}

function delay(ms: number, signal: EnhancedAbortSignal): Promise<void> {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(resolve, ms);
    signal.register(() => {
      clearTimeout(timeout);
      reject(new AbortError('Operation aborted'));
    });
  });
}

app.listen(3000);

Key Benefits

  • Automatic Timeout: Requests automatically timeout after a specified duration
  • Resource Cleanup: Automatic cleanup when requests finish or are cancelled
  • Error Handling: Proper error handling for aborted operations
  • Manual Cancellation: Support for manual request cancellation
  • Linked Controllers: Combine user cancellation with timeout

Released under the MIT License. If you find this project helpful, consider buying me a coffee