{"id":46198538,"url":"https://github.com/whytrchy/nestjs-aborter","last_synced_at":"2026-03-03T04:08:24.658Z","repository":{"id":323904369,"uuid":"1084928818","full_name":"whytrchy/nestjs-aborter","owner":"whytrchy","description":"NestJS module for request lifecycle management using AbortController. Handle client disconnections, timeouts, and graceful request cancellation.","archived":false,"fork":false,"pushed_at":"2025-11-22T04:50:45.000Z","size":166,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-22T06:18:00.235Z","etag":null,"topics":["abort-controller","nestjs","nestjs-module","nodejs","request-cancellation","signal-handling","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/whytrchy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-28T11:04:49.000Z","updated_at":"2025-11-22T04:50:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/whytrchy/nestjs-aborter","commit_stats":null,"previous_names":["whytrchy/nestjs-aborter"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/whytrchy/nestjs-aborter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whytrchy%2Fnestjs-aborter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whytrchy%2Fnestjs-aborter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whytrchy%2Fnestjs-aborter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whytrchy%2Fnestjs-aborter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/whytrchy","download_url":"https://codeload.github.com/whytrchy/nestjs-aborter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whytrchy%2Fnestjs-aborter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30031981,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-03T03:27:35.548Z","status":"ssl_error","status_checked_at":"2026-03-03T03:27:09.213Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abort-controller","nestjs","nestjs-module","nodejs","request-cancellation","signal-handling","typescript"],"created_at":"2026-03-03T04:08:24.211Z","updated_at":"2026-03-03T04:08:24.649Z","avatar_url":"https://github.com/whytrchy.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003ca href=\"http://nestjs.com/\" target=\"blank\"\u003e\u003cimg src=\"https://nestjs.com/img/logo-small.svg\" width=\"120\" alt=\"Nest Logo\" /\u003e\u003c/a\u003e\n\n# nestjs-aborter\n\n**Automatic request cancellation and timeout handling for NestJS**\n\n[![npm version](https://img.shields.io/npm/v/nestjs-aborter.svg)](https://www.npmjs.com/package/nestjs-aborter)\n[![npm downloads](https://img.shields.io/npm/dm/nestjs-aborter.svg)](https://www.npmjs.com/package/nestjs-aborter)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Build Status](https://img.shields.io/github/actions/workflow/status/whytrchy/nestjs-aborter/release.yml?branch=main)](https://github.com/whytrchy/nestjs-aborter/actions)\n[![Release](https://github.com/whytrchy/nestjs-aborter/actions/workflows/release.yml/badge.svg)](https://github.com/whytrchy/nestjs-aborter/actions/workflows/release.yml)\n[![codecov](https://codecov.io/gh/whytrchy/nestjs-aborter/branch/main/graph/badge.svg)](https://codecov.io/gh/whytrchy/nestjs-aborter)\n[![Maintainability](https://qlty.sh/gh/whytrchy/projects/nestjs-aborter/maintainability.svg)](https://qlty.sh/gh/whytrchy/projects/nestjs-aborter)\n[![semantic-release: conventionalcommits](https://img.shields.io/badge/semantic--release-conventionalcommits-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)\n[![Known Vulnerabilities](https://snyk.io/test/github/whytrchy/nestjs-aborter/badge.svg)](https://snyk.io/test/github/whytrchy/nestjs-aborter)\n\nStop wasting resources on abandoned requests. Automatically cancel operations when clients disconnect or requests timeout.\n\n\u003c/div\u003e\n\n---\n\n## Why This Package?\n\nWhen clients disconnect or requests timeout, your server continues processing—wasting CPU, memory, and database connections. This package automatically detects these scenarios and cancels ongoing operations, improving resource efficiency and performance under load.\n\n**Key Features:**\n\n- ✅ Automatic AbortController injection on every request\n- ✅ Multi-level timeout control (global, per-endpoint, per-operation)\n- ✅ Client disconnect detection\n- ✅ Zero dependencies, full TypeScript support\n\n## Installation\n\n```bash\nnpm install nestjs-aborter\n```\n\n## Quick Start\n\n### 1. Setup Module\n\nRegister the module globally and add the interceptor:\n\n```typescript\nimport { Module } from '@nestjs/common';\nimport { APP_INTERCEPTOR } from '@nestjs/core';\nimport { AborterModule, AborterInterceptor } from 'nestjs-aborter';\n\n@Module({\n  imports: [\n    AborterModule.forRoot({\n      timeout: 30000, // Global 30s timeout\n      skipRoutes: ['/health'], // Optional: skip specific routes\n    }),\n  ],\n  providers: [\n    {\n      provide: APP_INTERCEPTOR,\n      useClass: AborterInterceptor,\n    },\n  ],\n})\nexport class AppModule {}\n```\n\n### 2. Use in Controllers\n\nInject the `AbortSignal` into your route handlers and pass it to operations:\n\n```typescript\nimport { Controller, Get, Post } from '@nestjs/common';\nimport { AborterSignal, withAbort, RequestTimeout } from 'nestjs-aborter';\n\n@Controller('users')\nexport class UserController {\n  @Get()\n  async findAll(@AborterSignal() signal: AbortSignal) {\n    // Pass signal to external APIs\n    const response = await fetch('https://api.example.com/users', { signal });\n    return response.json();\n  }\n\n  @Post()\n  @RequestTimeout(10000) // Override global timeout for this endpoint\n  async create(\n    @Body() dto: CreateUserDto,\n    @AborterSignal() signal: AbortSignal,\n  ) {\n    // Wrap promises that don't natively support AbortSignal\n    return withAbort(this.userService.create(dto), signal);\n  }\n\n  @Get(':id')\n  async findOne(@Param('id') id: string, @AborterSignal() signal: AbortSignal) {\n    // Set timeout for specific operations\n    return withAbort(this.userService.findById(id), signal, { timeout: 5000 });\n  }\n}\n```\n\n## Configuration Options\n\n```typescript\nAborterModule.forRoot({\n  timeout: 30000, // Global timeout in milliseconds (optional)\n  reason: 'Request cancelled', // Custom abort reason\n  enableLogging: true, // Log abort events for debugging\n  skipRoutes: ['^/health$'], // Regex patterns for routes to skip\n  skipMethods: ['OPTIONS'], // HTTP methods to skip\n});\n```\n\n**Async configuration** with dependency injection:\n\n```typescript\nAborterModule.forRootAsync({\n  inject: [ConfigService],\n  useFactory: (config: ConfigService) =\u003e ({\n    timeout: config.get('REQUEST_TIMEOUT'),\n  }),\n});\n```\n\n## Timeout Hierarchy\n\nTimeouts are applied in priority order, with more specific timeouts overriding general ones:\n\n```typescript\n// 1. Global timeout (set in module config)\nAborterModule.forRoot({ timeout: 30000 })\n\n// 2. Endpoint timeout (overrides global)\n@Get() @RequestTimeout(10000) handler() {}\n\n// 3. Operation timeout (overrides both)\nwithAbort(promise, signal, { timeout: 5000 })\n```\n\n**Example combining all three levels:**\n\n```typescript\n@Get('dashboard')\n@RequestTimeout(15000) // Max 15s for entire endpoint\nasync getDashboard(@AborterSignal() signal: AbortSignal) {\n  // This operation has max 2s\n  const quick = await withAbort(this.fastApi(), signal, { timeout: 2000 });\n\n  // This operation has max 8s\n  const slow = await withAbort(this.slowApi(), signal, { timeout: 8000 });\n\n  return { quick, slow };\n}\n```\n\n## API Reference\n\n### Decorators\n\n**`@AborterSignal()`** - Injects the `AbortSignal` for the current request\n\n```typescript\nasync handler(@AborterSignal() signal: AbortSignal) { }\n```\n\n**`@RequestTimeout(milliseconds)`** - Overrides the global timeout for a specific endpoint\n\n```typescript\n@Get()\n@RequestTimeout(5000) // 5 second timeout\nasync handler() { }\n\n@Post('upload')\n@RequestTimeout(null) // Disable timeout entirely\nasync upload() { }\n```\n\n**`@AborterReason()`** - Injects the abort reason if the request was aborted\n\n```typescript\nasync handler(@AborterReason() reason?: string) { }\n```\n\n### Utilities\n\n**`withAbort\u003cT\u003e(promise, signal?, options?)`** - Wraps any promise with abort capability\n\n```typescript\n// Basic usage\nawait withAbort(someOperation(), signal);\n\n// With custom timeout\nawait withAbort(someOperation(), signal, { timeout: 3000 });\n```\n\n**`AbortError`** - Error thrown when operations are aborted\n\n```typescript\ntry {\n  await withAbort(operation(), signal);\n} catch (error) {\n  if (error instanceof AbortError) {\n    console.log('Aborted:', error.message);\n  }\n}\n```\n\n### Guards \u0026 Filters\n\n**`AborterGuard`** - Prevents execution if the request is already aborted\n\n```typescript\n@UseGuards(AborterGuard)\nexport class ApiController {}\n```\n\n**`AborterFilter`** - Handles timeout exceptions and returns proper HTTP status codes\n\n```typescript\n@Module({\n  providers: [\n    { provide: APP_FILTER, useClass: AborterFilter },\n  ],\n})\n```\n\nReturns:\n\n- **408 Request Timeout** when operation exceeds timeout\n- **499 Client Closed Request** when client disconnects\n\n## Common Patterns\n\n### External API Calls\n\nPass the signal to any HTTP client that supports it:\n\n```typescript\nawait fetch(url, { signal });\nawait axios.get(url, { signal });\n```\n\n### Database Operations\n\nWrap database queries to prevent wasted resources:\n\n```typescript\nasync findById(id: string, signal: AbortSignal) {\n  return withAbort(\n    this.prisma.user.findUnique({ where: { id } }),\n    signal,\n    { timeout: 3000 }\n  );\n}\n```\n\n### Parallel Operations\n\nAll operations abort together when timeout is reached:\n\n```typescript\n@Get('dashboard')\nasync getDashboard(@AborterSignal() signal: AbortSignal) {\n  return Promise.all([\n    withAbort(this.userService.getUsers(), signal),\n    withAbort(this.postService.getPosts(), signal),\n  ]);\n}\n```\n\n### Request-Scoped Services\n\nAccess the signal without manually passing it through service layers:\n\n```typescript\n@Injectable({ scope: Scope.REQUEST })\nexport class DataService {\n  constructor(@Inject(REQUEST) private request: RequestWithAbortController) {}\n\n  async process() {\n    const signal = this.request.abortController.signal;\n    return withAbort(this.heavyOperation(), signal);\n  }\n}\n```\n\nUseful when you have deep service chains and don't want to pass the signal manually.\n\n## Testing\n\n```typescript\nit('should handle aborted requests', async () =\u003e {\n  const controller = new AbortController();\n  controller.abort();\n\n  await expect(userController.findAll(controller.signal)).rejects.toThrow(\n    AbortError,\n  );\n});\n```\n\n## Best Practices\n\n- ✅ Always pass the signal to external API calls and long-running operations\n- ✅ Use `withAbort()` for promises that don't natively support AbortSignal\n- ✅ Set global timeout as a safety net, endpoint timeouts for specific routes, and operation timeouts for critical calls\n- ✅ Skip health checks and monitoring endpoints in configuration to reduce overhead\n- ✅ Use `AborterFilter` for consistent error handling\n\n## License\n\nMIT © [whytrchy](https://github.com/whytrchy)\n\n---\n\n[GitHub](https://github.com/whytrchy/nestjs-aborter) • [Issues](https://github.com/whytrchy/nestjs-aborter/issues) • [npm](https://www.npmjs.com/package/nestjs-aborter)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwhytrchy%2Fnestjs-aborter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwhytrchy%2Fnestjs-aborter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwhytrchy%2Fnestjs-aborter/lists"}