{"_id":"@animesh0764/pg-rate-limit","name":"@animesh0764/pg-rate-limit","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@animesh0764/pg-rate-limit","version":"0.1.0","description":"Postgres-powered rate limiting without Redis — works with Express, Fastify, Next.js, Hono","keywords":["rate-limit","rate-limiting","postgresql","postgres","express","fastify","nextjs","hono","middleware","no-redis"],"author":{"name":"Animesh Singh"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/Animesh0764/pg-rate-limit-npm-package.git"},"main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","typecheck":"tsc --noEmit","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"peerDependencies":{"pg":">=8.0.0"},"devDependencies":{"@types/pg":"^8.0.0","@vitest/coverage-v8":"^2.0.0","pg":"^8.0.0","tsup":"^8.0.0","typescript":"^5.0.0","vitest":"^2.0.0"},"_id":"@animesh0764/pg-rate-limit@0.1.0","gitHead":"94add4b0219dbbb27d1a05c16ca4a88909eb98fe","bugs":{"url":"https://github.com/Animesh0764/pg-rate-limit-npm-package/issues"},"homepage":"https://github.com/Animesh0764/pg-rate-limit-npm-package#readme","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-QYOGlzBnygHdiGw0QYP9oe0B6yF959JmN2GRgkkD9WKKc6fx6XSuc6FLI9n4PrnkSRfTMWGHrg5oKAcX5QMvzQ==","shasum":"68c090f6d2123457a321e62cec8c671be19698c3","tarball":"https://registry.npmjs.org/@animesh0764/pg-rate-limit/-/pg-rate-limit-0.1.0.tgz","fileCount":9,"unpackedSize":86669,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@animesh0764%2fpg-rate-limit@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC0fmmDjdf2x+LaMCchgKRaRqUD6U+7qISgNZv+8NHUTgIgWVsflJ0exf/JMoSaEwvjhvhPXEfzOxJydU8Dk8+uyjk="}]},"_npmUser":{"name":"animesh0764","email":"animeshbksingh@outlook.com"},"directories":{},"maintainers":[{"name":"animesh0764","email":"animeshbksingh@outlook.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pg-rate-limit_0.1.0_1780635582446_0.9808895050339252"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-05T04:59:42.290Z","0.1.0":"2026-06-05T04:59:42.570Z","modified":"2026-06-05T04:59:42.935Z"},"maintainers":[{"name":"animesh0764","email":"animeshbksingh@outlook.com"}],"description":"Postgres-powered rate limiting without Redis — works with Express, Fastify, Next.js, Hono","homepage":"https://github.com/Animesh0764/pg-rate-limit-npm-package#readme","keywords":["rate-limit","rate-limiting","postgresql","postgres","express","fastify","nextjs","hono","middleware","no-redis"],"repository":{"type":"git","url":"git+https://github.com/Animesh0764/pg-rate-limit-npm-package.git"},"author":{"name":"Animesh Singh"},"bugs":{"url":"https://github.com/Animesh0764/pg-rate-limit-npm-package/issues"},"license":"MIT","readme":"# pg-rate-limit\n\nPostgres-powered rate limiting without Redis.\n\nWorks with Express, Fastify, Next.js, and Hono. No Redis, no extra infrastructure — just the PostgreSQL you already have.\n\n## Install\n\n```bash\nnpm install @animesh0764/pg-rate-limit\n```\n\n`pg` is a peer dependency — install it if you haven't already:\n\n```bash\nnpm install pg\n```\n\n## Quick start\n\n```ts\nimport { Pool } from 'pg'\nimport { createRateLimiter } from '@animesh0764/pg-rate-limit'\n\nconst pool = new Pool({ connectionString: process.env.DATABASE_URL })\n\nconst limiter = createRateLimiter({\n  pool,\n  limit: 100,\n  window: '1m',\n})\n\n// Run once on startup to create the required tables\nawait limiter.setup()\n```\n\n## Framework examples\n\n### Express\n\n```ts\n// Global middleware — applies to every route\napp.use(limiter.express())\n\n// Custom identifier (e.g. authenticated user ID)\napp.use(limiter.express({\n  identify: (req) => req.user?.id ?? req.ip ?? 'anonymous',\n}))\n\n// Skip rate limiting for certain requests\napp.use(limiter.express({\n  skip: (req) => req.path === '/health',\n}))\n\n// Per-route with different limits\nconst strictLimiter = createRateLimiter({ pool, limit: 10, window: '1m' })\napp.post('/auth/login', strictLimiter.express(), handler)\n```\n\n### Fastify\n\n```ts\n// As a preHandler hook\nfastify.addHook('preHandler', limiter.fastify())\n\n// Route-level\nfastify.post('/api/action', {\n  preHandler: strictLimiter.fastify(),\n  handler: async (request, reply) => { ... }\n})\n```\n\n### Next.js (App Router)\n\n```ts\n// app/api/data/route.ts\nimport { NextResponse } from 'next/server'\n\nexport const GET = limiter.nextjs()(async (req) => {\n  return NextResponse.json({ data: 'hello' })\n})\n```\n\n### Hono\n\n```ts\n// Global\napp.use('*', limiter.hono())\n\n// Route group\napp.use('/api/*', limiter.hono({\n  identify: (c) => c.req.header('authorization') ?? 'anonymous',\n}))\n```\n\n## Configuration\n\n```ts\nconst limiter = createRateLimiter({\n  pool,           // pg.Pool instance (required)\n  limit: 100,     // max requests per window (required)\n  window: '1m',   // time window (required)\n  strategy: 'sliding',       // 'sliding' (default) or 'fixed'\n  prefix: 'rl',              // key prefix, default 'rl'\n  tableName: 'rate_limit',   // table name prefix, default 'rate_limit'\n})\n```\n\n### Window formats\n\n| Format | Meaning |\n|--------|---------|\n| `'500ms'` | 500 milliseconds |\n| `'30s'` | 30 seconds |\n| `'1m'` | 1 minute |\n| `'1h'` | 1 hour |\n| `'7d'` | 7 days |\n| `60000` | 60 seconds (raw ms) |\n\n## Strategies\n\n### Sliding window (default)\n\nCounts requests in the last N seconds. Accurate but uses more database rows (one per request, deleted after the window expires).\n\n**Best for:** API rate limits, login throttling, anything where accuracy matters.\n\n### Fixed window\n\nDivides time into fixed buckets (e.g. minute `:00–:59`). Faster — one row per `(identifier, window)` pair. Has a known burst edge case: up to 2× the limit can pass when two bursts straddle a window boundary.\n\n**Best for:** High-throughput scenarios where approximate limiting is acceptable.\n\n## Response headers\n\nEvery response includes standard rate limit headers:\n\n| Header | Value |\n|--------|-------|\n| `X-RateLimit-Limit` | Configured limit |\n| `X-RateLimit-Remaining` | Requests left in this window |\n| `X-RateLimit-Reset` | Epoch second when the window resets |\n| `Retry-After` | Seconds until retry (only on 429 responses) |\n\n## Programmatic check\n\n```ts\nconst result = await limiter.check('user:abc123')\n// { allowed: true, total: 100, remaining: 67, resetAt: Date }\n\nif (!result.allowed) {\n  // handle rate limit in your own way\n}\n```\n\n## Database setup\n\n### Tables created by `setup()`\n\n**`rate_limit_fixed`** — used by the fixed window strategy:\n```sql\nidentifier   TEXT\nwindow_start TIMESTAMPTZ  -- start of the current window\nwindow_end   TIMESTAMPTZ  -- end of the current window\ncount        INTEGER      -- requests in this window\n```\n\n**`rate_limit_sliding`** — used by the sliding window strategy:\n```sql\nidentifier   TEXT\nrequested_at TIMESTAMPTZ  -- when the request arrived\n```\n\n### Cleanup\n\nOld records accumulate over time. Call `cleanup()` periodically:\n\n```ts\n// Run once per hour (e.g. with a cron job)\nawait limiter.cleanup()\n```\n\nOr schedule it with `setInterval`:\n```ts\nsetInterval(() => limiter.cleanup(), 60 * 60 * 1000)\n```\n\n## Why not Redis?\n\nRedis is excellent for rate limiting but adds operational overhead:\n\n- One more service to deploy, monitor, and back up\n- More infrastructure cost\n- Connection management, eviction policies, persistence configuration\n\nIf you already have PostgreSQL and don't need sub-millisecond performance, `pg-rate-limit` gives you production-ready rate limiting with zero additional infrastructure.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-10fc5bc473a44a24d235ac2754155199"}