{"_id":"@anysk/nestjs-redis-throttler","name":"@anysk/nestjs-redis-throttler","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@anysk/nestjs-redis-throttler","version":"1.0.0","description":"Redis-backed rate-limiting guard for NestJS with per-route overrides. No @nestjs/throttler dependency — fresh Nest peers, atomic Lua fixed windows, bring your own Redis client.","license":"MIT","author":{"name":"AnySk"},"repository":{"type":"git","url":"git+https://github.com/AnySk/nestjs-redis-throttler.git"},"keywords":["nestjs","rate-limit","rate-limiting","throttler","throttle","redis","guard"],"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"./package.json":"./package.json"},"publishConfig":{"access":"public"},"packageManager":"pnpm@10.24.0","engines":{"node":">=20"},"scripts":{"build":"tsup","check-types":"tsc --noEmit","test":"jest","prepublishOnly":"pnpm check-types && pnpm test && pnpm build"},"peerDependencies":{"@nestjs/common":"^11.0.0 || ^12.0.0","@nestjs/core":"^11.0.0 || ^12.0.0","reflect-metadata":"^0.2.2"},"devDependencies":{"@nestjs/common":"^11.1.28","@nestjs/core":"^11.1.28","@nestjs/platform-express":"^11.1.28","@nestjs/testing":"^11.1.28","@swc/core":"^1.16.1","@swc/jest":"^0.2.39","@types/jest":"30.0.0","@types/node":"^26.4.0","@types/supertest":"^7.2.1","jest":"30.4.2","reflect-metadata":"^0.2.2","rxjs":"^7.8.2","supertest":"^7.2.2","tsup":"^8.5.1","typescript":"~5.9.3"},"pnpm":{"onlyBuiltDependencies":["@swc/core","esbuild"]},"jest":{"testEnvironment":"node","rootDir":"src","testRegex":".*\\.spec\\.ts$","moduleFileExtensions":["js","json","ts"],"transform":{"^.+\\.(t|j)s$":["@swc/jest",{"jsc":{"target":"es2021","parser":{"syntax":"typescript","decorators":true},"transform":{"legacyDecorator":true,"decoratorMetadata":true}},"module":{"type":"commonjs"}}]}},"gitHead":"334423fe0ec3067398a9e62b34c2eb31358d013c","_id":"@anysk/nestjs-redis-throttler@1.0.0","bugs":{"url":"https://github.com/AnySk/nestjs-redis-throttler/issues"},"homepage":"https://github.com/AnySk/nestjs-redis-throttler#readme","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-ERvzW9vF3YhRlxCiMdr2Jt3jwalgh1QLhqgB4pzHebdwWTRuzJ2bXTLpwu6LmffhsBVWIAhLyWNJoupbdqr2HA==","shasum":"93b55530a8fc5008cada315f4f67e445a4ac091f","tarball":"https://registry.npmjs.org/@anysk/nestjs-redis-throttler/-/nestjs-redis-throttler-1.0.0.tgz","fileCount":9,"unpackedSize":72692,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDSTL7epKliJIIiObhlGrDsY0CzJc0TiWSM7NmKvD6CxAIhAMHjgFQryax9Ul2+bRJRRsONwC5LKaiAA6yhnrSHgvjW"}]},"_npmUser":{"name":"anysk","email":"anysk81@gmail.com"},"directories":{},"maintainers":[{"name":"anysk","email":"anysk81@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nestjs-redis-throttler_1.0.0_1787845892226_0.9214098106501891"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-27T15:51:31.910Z","1.0.0":"2026-08-27T15:51:32.374Z","modified":"2026-08-27T15:51:32.675Z"},"maintainers":[{"name":"anysk","email":"anysk81@gmail.com"}],"description":"Redis-backed rate-limiting guard for NestJS with per-route overrides. No @nestjs/throttler dependency — fresh Nest peers, atomic Lua fixed windows, bring your own Redis client.","homepage":"https://github.com/AnySk/nestjs-redis-throttler#readme","keywords":["nestjs","rate-limit","rate-limiting","throttler","throttle","redis","guard"],"repository":{"type":"git","url":"git+https://github.com/AnySk/nestjs-redis-throttler.git"},"author":{"name":"AnySk"},"bugs":{"url":"https://github.com/AnySk/nestjs-redis-throttler/issues"},"license":"MIT","readme":"# @anysk/nestjs-redis-throttler\n\nRedis-backed rate limiting for NestJS — a standalone guard with per-route overrides and **no `@nestjs/throttler` dependency**.\n\nWhy it exists: rate-limiting packages that wrap or extend `@nestjs/throttler` inherit its peer range, so every Nest major leaves you waiting. This package depends only on `@nestjs/common` / `@nestjs/core` (peers: **`^11 || ^12`**) and a tiny storage contract you can implement for any client. Counters live in Redis via one atomic Lua `EVAL`, so limits are shared across instances and survive restarts — which the default in-memory throttler storage never was.\n\n- Fixed-window counting, atomic (INCR + PEXPIRE in one script; no TTL-less stray counters)\n- `@Throttle()` / `@SkipThrottle()` per-route overrides, same object shape as `@nestjs/throttler`\n- Multiple named windows (e.g. a minute burst limit plus an hourly cap)\n- 429 responses carry `Retry-After`\n- Only HTTP contexts are throttled by default — websocket/microservice/bot-framework contexts pass through\n- Bring-your-own Redis client (structurally matches node-redis v4+; one-line ioredis adapter)\n- Fail-open by default when Redis is briefly unavailable (configurable)\n- Ships dual CJS + ESM, works under compilers that don't emit decorator metadata (esbuild/swc)\n\n## Install\n\n```bash\nnpm i @anysk/nestjs-redis-throttler\n# peers you already have in a Nest app: @nestjs/common @nestjs/core reflect-metadata\n```\n\n## Quickstart\n\n```ts\n// app.module.ts\nimport { APP_GUARD } from '@nestjs/core';\nimport { RedisThrottlerModule, RedisThrottlerGuard, RedisStore } from '@anysk/nestjs-redis-throttler';\n\n@Module({\n  imports: [\n    RedisThrottlerModule.forRootAsync({\n      imports: [RedisModule],\n      inject: [RedisService],\n      useFactory: (redis: RedisService) => ({\n        windows: [\n          { name: 'default', ttl: 60_000, limit: 60 },\n          { name: 'hourly', ttl: 3_600_000, limit: 10_000 },\n        ],\n        store: new RedisStore(redis.client), // any node-redis v4+ client\n      }),\n    }),\n  ],\n  providers: [{ provide: APP_GUARD, useClass: RedisThrottlerGuard }],\n})\nexport class AppModule {}\n```\n\nPer-route overrides, by window name:\n\n```ts\nimport { Throttle, SkipThrottle } from '@anysk/nestjs-redis-throttler';\n\n@Controller('account')\nexport class AccountController {\n  @Throttle({ default: { ttl: 60_000, limit: 3 } }) // tighten the minute window here\n  @Post('sensitive')\n  sensitive() {}\n\n  @SkipThrottle()\n  @Get('health')\n  health() {}\n}\n```\n\nAn override name that matches no configured window defines an **extra window for that route only** (both `ttl` and `limit` required):\n\n```ts\n@Throttle({ burst: { ttl: 1_000, limit: 2 } })\n```\n\n## Options\n\n| Option | Default | Meaning |\n| --- | --- | --- |\n| `windows` | — | Named windows enforced on every route. `ttl` in ms. |\n| `store` | — | `RedisStore` in production; `InMemoryStore` for tests/dev. |\n| `keyPrefix` | `\"nrt\"` | Redis key prefix. Keys look like `nrt:<window>:<Controller>:<handler>:<tracker>`. |\n| `contextTypes` | `[\"http\"]` | Execution context types to throttle; everything else passes through. |\n| `trustProxy` | `false` | Key on the first `X-Forwarded-For` hop instead of `req.ip`. Prefer configuring your HTTP adapter's `trust proxy`; this is the escape hatch when you can't. |\n| `getTracker` | `req.ip` | Custom client identity (e.g. user id). Return `\"\"` to skip the request. |\n| `skipIf` | — | Predicate over the `ExecutionContext` to bypass throttling. |\n| `errorMessage` | `\"Too Many Requests\"` | 429 body message, or a factory receiving the exceeded-limit detail. |\n| `onStoreError` | `\"allow\"` | `\"allow\"` logs and lets requests through when the store throws; `\"block\"` rethrows. |\n\nRequests with no resolvable identity are **allowed**, not pooled into one shared bucket — a misconfigured proxy shouldn't rate-limit all users collectively.\n\n## ioredis\n\n```ts\nnew RedisStore({\n  eval: (script, { keys, arguments: args }) => ioredis.eval(script, keys.length, ...keys, ...args),\n});\n```\n\nAnything implementing `ThrottlerStore` (`increment(key, ttlMs) → { totalHits, timeToExpireMs }`) works as a store; the contract requires atomic counting where the first hit starts the TTL.\n\n## Migrating from @nestjs/throttler\n\n```diff\n- ThrottlerModule.forRoot([\n-   { name: 'default', ttl: 60_000, limit: 60 },\n-   { name: 'hourly', ttl: 3_600_000, limit: 10_000 },\n- ]),\n+ RedisThrottlerModule.forRootAsync({\n+   imports: [RedisModule],\n+   inject: [RedisService],\n+   useFactory: (redis: RedisService) => ({\n+     windows: [\n+       { name: 'default', ttl: 60_000, limit: 60 },\n+       { name: 'hourly', ttl: 3_600_000, limit: 10_000 },\n+     ],\n+     store: new RedisStore(redis.client),\n+   }),\n+ }),\n```\n\n- `@Throttle({ default: { ttl, limit } })` and `@SkipThrottle()` keep their shape — update the import.\n- A custom \"http-only\" guard subclass becomes configuration: non-HTTP contexts are skipped out of the box (`contextTypes`).\n- Behavior parity: buckets are per window × route × client, 429 with `Retry-After`. Difference: counters are in Redis, so limits hold across instances and restarts.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-8b8c72f615920a97c2976358cb645737"}