Six frameworks. Four storage backends. One import. Zero dependencies.

Published: (February 22, 2026 at 12:31 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Cover image for Six frameworks. Four storage backends. One import. Zero dependencies.

Same API everywhere

// Express
import { hitlimit } from '@joint-ops/hitlimit';
app.use(hitlimit({ limit: 100, window: '1m' }));

// Fastify
import { hitlimit } from '@joint-ops/hitlimit/fastify';
await app.register(hitlimit, { limit: 100, window: '1m' });

// Hono
import { hitlimit } from '@joint-ops/hitlimit/hono';
app.use(hitlimit({ limit: 100, window: '1m' }));

// NestJS
import { HitLimitModule, HitLimitGuard } from '@joint-ops/hitlimit/nest';
@Module({
  imports: [HitLimitModule.register({ limit: 100, window: '1m' })],
  providers: [{ provide: APP_GUARD, useClass: HitLimitGuard }],
})
export class AppModule {}

// Bun.serve
import { hitlimit } from '@joint-ops/hitlimit-bun';
Bun.serve({ fetch: hitlimit({ limit: 100, window: '1m' }, handler) });

// Elysia
import { hitlimit } from '@joint-ops/hitlimit-bun/elysia';
new Elysia().use(hitlimit({ limit: 100, window: '1m' })).listen(3000);

Switch frameworks tomorrow. Your rate‑limiting code stays the same.

4 Storage Backends Built In

// Memory (default) – fastest, no setup
app.use(hitlimit({ limit: 100, window: '1m' }));

// SQLite – survives restarts
app.use(hitlimit({ store: sqliteStore({ path: './limits.db' }), limit: 100, window: '1m' }));

// Redis – distributed across instances
app.use(hitlimit({ store: redisStore({ url: process.env.REDIS_URL }), limit: 100, window: '1m' }));

// Postgres – use your existing database
app.use(hitlimit({ store: postgresStore({ pool }), limit: 100, window: '1m' }));

Start with memory on day one. Move to Postgres or Redis when you scale. Nothing else changes.

New in v1.2.0: PostgreSQL Store

import { postgresStore } from '@joint-ops/hitlimit/stores/postgres';
import { Pool } from 'pg';

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

app.use(
  hitlimit({
    limit: 100,
    window: '1m',
    store: postgresStore({ pool }),
  })
);

One atomic query per request. Zero race conditions. Tables are created automatically on first run. Named prepared statements give 30‑40 % lower latency. No new infrastructure to manage.

Fast

Node.js (10K unique IPs)
Memory       3.16M ops/s   316ns
SQLite       352K ops/s   2.8µs
Redis        6.7K ops/s   149µs
Postgres     3.0K ops/s   336µs

Bun (10K unique IPs)
Memory       8.32M ops/s   120ns
bun:sqlite   325K ops/s   3.1µs
Redis        6.7K ops/s   148µs
Postgres     3.7K ops/s   273µs

Peak: 12.38 M ops/s on Bun. 4.83 M ops/s on Node.js.

All benchmarks are open source: .

More Than a Counter

  • Tiered limits for free, pro and enterprise plans.
  • Auto‑ban repeat offenders after N violations.
  • Group limits for per‑team or per‑org quotas.
  • Skip rules for health checks, admins, internal routes.
  • Human‑readable windows like '15m', '1h', '1d' instead of milliseconds.

All built in. All zero dependencies.

Get Started

npm install @joint-ops/hitlimit    # Node.js
bun add @joint-ops/hitlimit-bun    # Bun

Docs | npm | Release notes

If it saves you time, give it a star.

0 views
Back to Blog

Related posts

Read more »