npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@animesh0764/pg-rate-limit

v0.1.0

Published

Postgres-powered rate limiting without Redis — works with Express, Fastify, Next.js, Hono

Readme

pg-rate-limit

Postgres-powered rate limiting without Redis.

Works with Express, Fastify, Next.js, and Hono. No Redis, no extra infrastructure — just the PostgreSQL you already have.

Install

npm install @animesh0764/pg-rate-limit

pg is a peer dependency — install it if you haven't already:

npm install pg

Quick start

import { Pool } from 'pg'
import { createRateLimiter } from '@animesh0764/pg-rate-limit'

const pool = new Pool({ connectionString: process.env.DATABASE_URL })

const limiter = createRateLimiter({
  pool,
  limit: 100,
  window: '1m',
})

// Run once on startup to create the required tables
await limiter.setup()

Framework examples

Express

// Global middleware — applies to every route
app.use(limiter.express())

// Custom identifier (e.g. authenticated user ID)
app.use(limiter.express({
  identify: (req) => req.user?.id ?? req.ip ?? 'anonymous',
}))

// Skip rate limiting for certain requests
app.use(limiter.express({
  skip: (req) => req.path === '/health',
}))

// Per-route with different limits
const strictLimiter = createRateLimiter({ pool, limit: 10, window: '1m' })
app.post('/auth/login', strictLimiter.express(), handler)

Fastify

// As a preHandler hook
fastify.addHook('preHandler', limiter.fastify())

// Route-level
fastify.post('/api/action', {
  preHandler: strictLimiter.fastify(),
  handler: async (request, reply) => { ... }
})

Next.js (App Router)

// app/api/data/route.ts
import { NextResponse } from 'next/server'

export const GET = limiter.nextjs()(async (req) => {
  return NextResponse.json({ data: 'hello' })
})

Hono

// Global
app.use('*', limiter.hono())

// Route group
app.use('/api/*', limiter.hono({
  identify: (c) => c.req.header('authorization') ?? 'anonymous',
}))

Configuration

const limiter = createRateLimiter({
  pool,           // pg.Pool instance (required)
  limit: 100,     // max requests per window (required)
  window: '1m',   // time window (required)
  strategy: 'sliding',       // 'sliding' (default) or 'fixed'
  prefix: 'rl',              // key prefix, default 'rl'
  tableName: 'rate_limit',   // table name prefix, default 'rate_limit'
})

Window formats

| Format | Meaning | |--------|---------| | '500ms' | 500 milliseconds | | '30s' | 30 seconds | | '1m' | 1 minute | | '1h' | 1 hour | | '7d' | 7 days | | 60000 | 60 seconds (raw ms) |

Strategies

Sliding window (default)

Counts requests in the last N seconds. Accurate but uses more database rows (one per request, deleted after the window expires).

Best for: API rate limits, login throttling, anything where accuracy matters.

Fixed window

Divides 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.

Best for: High-throughput scenarios where approximate limiting is acceptable.

Response headers

Every response includes standard rate limit headers:

| Header | Value | |--------|-------| | X-RateLimit-Limit | Configured limit | | X-RateLimit-Remaining | Requests left in this window | | X-RateLimit-Reset | Epoch second when the window resets | | Retry-After | Seconds until retry (only on 429 responses) |

Programmatic check

const result = await limiter.check('user:abc123')
// { allowed: true, total: 100, remaining: 67, resetAt: Date }

if (!result.allowed) {
  // handle rate limit in your own way
}

Database setup

Tables created by setup()

rate_limit_fixed — used by the fixed window strategy:

identifier   TEXT
window_start TIMESTAMPTZ  -- start of the current window
window_end   TIMESTAMPTZ  -- end of the current window
count        INTEGER      -- requests in this window

rate_limit_sliding — used by the sliding window strategy:

identifier   TEXT
requested_at TIMESTAMPTZ  -- when the request arrived

Cleanup

Old records accumulate over time. Call cleanup() periodically:

// Run once per hour (e.g. with a cron job)
await limiter.cleanup()

Or schedule it with setInterval:

setInterval(() => limiter.cleanup(), 60 * 60 * 1000)

Why not Redis?

Redis is excellent for rate limiting but adds operational overhead:

  • One more service to deploy, monitor, and back up
  • More infrastructure cost
  • Connection management, eviction policies, persistence configuration

If you already have PostgreSQL and don't need sub-millisecond performance, pg-rate-limit gives you production-ready rate limiting with zero additional infrastructure.

License

MIT