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

@clov-std/elysia-ratelimit

v4.0.18

Published

Rate limiting for Elysia as a route macro, with RFC 9457 429 responses and pluggable storage.

Readme

Clov Elysia Rate Limit

Rate limiting for Elysia, as a macro. Put rateLimit: { limit: 10, window: 60 } on a route, guard or group and you're done.

It runs in transform, the first per-route hook, so excess requests are rejected before auth guards or handlers do any work. Counters are per client IP by default, which suits login endpoints. When lots of users share one IP (an office, a corporate proxy), a keyGenerator lets you count per token, session, API key, or whatever fits.

Storage goes through @clov-std/kv-store: memory by default, Redis when you need it, same routes either way.

bun add @clov-std/elysia-ratelimit elysia typebox

elysia (v2) and typebox are peer dependencies.

Usage

Basic

limit is the max number of requests, window is in seconds.

import { rateLimitPlugin } from '@clov-std/elysia-ratelimit';
import { Elysia } from 'elysia';

new Elysia()
	.use(rateLimitPlugin())
	.post(
		'/auth/login',
		{ rateLimit: { limit: 10, window: 60 } }, // 10 requests per minute per IP
		() => authenticate()
	)
	.listen(3000);

Redis

Pass a BunRedisStore (or any KvStore adapter) to keep counters across restarts and share them between instances.

import { rateLimitPlugin } from '@clov-std/elysia-ratelimit';
import { BunRedisStore } from '@clov-std/kv-store';
import { Elysia } from 'elysia';

const store = new BunRedisStore('redis://localhost:6379');

new Elysia()
	.use(rateLimitPlugin(store))
	.post('/auth/login', { rateLimit: { limit: 10, window: 60 } }, () => authenticate())
	.listen(3000);

Per user instead of per IP

For authenticated routes behind a shared IP, give each user their own counter:

import { rateLimitPlugin } from '@clov-std/elysia-ratelimit';
import { Elysia } from 'elysia';

new Elysia()
	.use(rateLimitPlugin())
	.get(
		'/api/data',
		{
			rateLimit: {
				limit: 100,
				window: 60,
				keyGenerator: ({ ip, request }) =>
					`${ip}:${request.headers.get('authorization') ?? ip}`
			}
		},
		() => getData()
	)
	.listen(3000);

extractClientIp is exported if you need it in your own keyGenerator.

Headers and the 429

Every response gets X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.

Once the limit is hit, the plugin throws Elysia's problem(), so the 429 is a full RFC 9457 document (application/problem+json) without any exception class to import:

{
	"type": "rate-limit.quota.exceeded",
	"title": "Too Many Requests",
	"status": 429,
	"detail": "Limit of 100 requests per 60s exceeded. Retry in 42s."
}
  • type is always RATE_LIMIT_ERROR_CODES.QUOTA_EXCEEDED.
  • title comes from Elysia, based on the status.
  • detail reflects the current counter, so it changes from one 429 to the next.

The headers are set before the throw, so they're still on the 429. The macro also declares this response for OpenAPI, so typed clients know about it.

Docs

API reference: clovlabs.github.io/std

License

MIT, see LICENSE.md.