Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/content/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export default withMermaid(defineConfig({
{ text: 'Encryption', link: '/docs/helpers/encryption' },
{ text: 'Form Data', link: '/docs/helpers/form-data' },
{ text: 'Publisher', link: '/docs/helpers/publisher' },
{ text: 'Ratelimit', link: '/docs/helpers/ratelimit' },
{ text: 'Signing', link: '/docs/helpers/signing' },
],
},
Expand Down
229 changes: 229 additions & 0 deletions apps/content/docs/helpers/ratelimit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
---
title: Rate Limit
description: Rate limiting features for oRPC with multiple adapters support.
---

# Rate Limit

The Rate Limit package provides flexible rate limiting for oRPC with multiple storage backend support. It includes adapters for in-memory, Redis, and Upstash, along with middleware and plugin helpers for seamless integration.

## Installation

::: code-group

```sh [npm]
npm install @orpc/experimental-ratelimit@latest
```

```sh [yarn]
yarn add @orpc/experimental-ratelimit@latest
```

```sh [pnpm]
pnpm add @orpc/experimental-ratelimit@latest
```

```sh [bun]
bun add @orpc/experimental-ratelimit@latest
```

```sh [deno]
deno add npm:@orpc/experimental-ratelimit@latest
```

:::

## Available Adapters

### Memory Adapter

A simple in-memory rate limiter using a sliding window log algorithm. Ideal for single-instance applications or development.

```ts
import { MemoryRatelimiter } from '@orpc/experimental-ratelimit/memory'

const limiter = new MemoryRatelimiter({
maxRequests: 10, // Maximum requests allowed
window: 60000, // Time window in milliseconds (60 seconds)
})
```

### Redis Adapter

Redis-based rate limiter using atomic Lua scripts for distributed rate limiting.

```ts
import { RedisRatelimiter } from '@orpc/experimental-ratelimit/redis'
import { Redis } from 'ioredis'

const redis = new Redis('redis://localhost:6379')

const limiter = new RedisRatelimiter({
eval: async (script, numKeys, ...rest) => {
return redis.eval(script, numKeys, ...rest)
},
maxRequests: 100,
window: 60000,
prefix: 'orpc:ratelimit:', // Optional key prefix
})
```

::: info
You can use any Redis client that supports Lua script evaluation by providing an `eval` function.
:::

### Upstash Adapter

Adapter for [@upstash/ratelimit](https://www.npmjs.com/package/@upstash/ratelimit), optimized for serverless environments like Vercel Edge and Cloudflare Workers.

```ts
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
import { UpstashRatelimiter } from '@orpc/experimental-ratelimit/upstash-ratelimit'

const redis = Redis.fromEnv()

const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '60 s'),
prefix: 'my-app:',
})

const limiter = new UpstashRatelimiter(ratelimit)
```

::: tip Edge Runtime Support
For Edge runtime like Vercel Edge or Cloudflare Workers, pass the `waitUntil` function to better handle background tasks:

```ts
const limiter = new UpstashRatelimiter(ratelimit, {
waitUntil: ctx.waitUntil.bind(ctx),
})
```

:::

## Blocking Mode

Some adapters support blocking mode, which waits for the rate limit to reset instead of immediately rejecting requests.

```ts
const limiter = new MemoryRatelimiter({
maxRequests: 10,
window: 60000,
blockingUntilReady: {
enabled: true,
timeout: 5000, // Wait up to 5 seconds
},
})
```

## Manual Usage

You can use adapters directly without middleware for custom rate limiting logic:

```ts twoslash
import { MemoryRatelimiter } from '@orpc/experimental-ratelimit/memory'
import { ORPCError } from '@orpc/server'

const limiter = new MemoryRatelimiter({
maxRequests: 5,
window: 60000,
})

const result = await limiter.limit('user:123')

if (!result.success) {
throw new ORPCError('TOO_MANY_REQUESTS', {
data: {
limit: result.limit,
remaining: result.remaining,
reset: result.reset,
},
})
}
```

## `createRatelimitMiddleware`

The `createRatelimitMiddleware` helper creates middleware for oRPC procedures to enforce rate limits.

```ts twoslash
import { call, os } from '@orpc/server'
import { MemoryRatelimiter } from '@orpc/experimental-ratelimit/memory'
import { createRatelimitMiddleware, Ratelimiter } from '@orpc/experimental-ratelimit'
import { z } from 'zod'

const loginProcedure = os
.$context<{ ratelimiter: Ratelimiter }>()
.input(z.object({ email: z.email() }))
.use(
createRatelimitMiddleware({
limiter: ({ context }) => context.ratelimiter,
key: ({ context }, input) => `login:${input.email}`,
}),
)
Comment thread
dinwwwh marked this conversation as resolved.
.handler(({ input }) => {
return { success: true }
})

const ratelimiter = new MemoryRatelimiter({
maxRequests: 10,
window: 60000,
})

const result = await call(
loginProcedure,
{ email: 'user@example.com' },
{ context: { ratelimiter } }
)
```

::: info Automatic Deduplication
The `createRatelimitMiddleware` automatically deduplicates rate limit checks when the same `limiter` and `key` combination is used multiple times in a request chain. This behavior follows the [Dedupe Middleware Best Practice](/docs/best-practices/dedupe-middleware). To disable deduplication, set the `dedupe: false` option.
:::

::: tip Conditional Limiter
You can dynamically choose different limiters based on context:

```ts
const premiumLimiter = new MemoryRatelimiter({
maxRequests: 100,
window: 60000,
})

const standardLimiter = new MemoryRatelimiter({
maxRequests: 10,
window: 60000,
})

const result = await call(
loginProcedure,
{ email: 'user@example.com' },
{
context: {
ratelimiter: isPremiumUser ? premiumLimiter : standardLimiter,
Comment thread
dinwwwh marked this conversation as resolved.
},
},
)
```

:::

## Handler Plugin

The `RatelimitHandlerPlugin` automatically adds HTTP rate-limiting headers (`RateLimit-*` and `Retry-After`) to responses when used with middleware created by [`createRatelimitMiddleware`](#createratelimitmiddleware).

```ts
import { RatelimitHandlerPlugin } from '@orpc/experimental-ratelimit'

const handler = new RPCHandler(router, {
plugins: [
new RatelimitHandlerPlugin(),
],
})
```

::: info
The `handler` can be any supported oRPC handler, such as [RPCHandler](/docs/rpc-handler), [OpenAPIHandler](/docs/openapi/openapi-handler), or other custom handlers.
:::
1 change: 1 addition & 0 deletions apps/content/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@orpc/client": "workspace:*",
"@orpc/contract": "workspace:*",
"@orpc/experimental-publisher": "workspace:*",
"@orpc/experimental-ratelimit": "workspace:*",
"@orpc/experimental-react-swr": "workspace:*",
"@orpc/openapi": "workspace:*",
"@orpc/openapi-client": "workspace:*",
Expand Down
26 changes: 26 additions & 0 deletions packages/ratelimit/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Hidden folders and files
.*
!.gitignore
!.*.example

# Common generated folders
logs/
node_modules/
out/
dist/
dist-ssr/
build/
coverage/
temp/

# Common generated files
*.log
*.log.*
*.tsbuildinfo
*.vitest-temp.json
vite.config.ts.timestamp-*
vitest.config.ts.timestamp-*

# Common manual ignore files
*.local
*.pem
81 changes: 81 additions & 0 deletions packages/ratelimit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<div align="center">
<image align="center" src="https://orpc.unnoq.com/logo.webp" width=280 alt="oRPC logo" />
</div>

<h1></h1>

<div align="center">
<a href="https://codecov.io/gh/unnoq/orpc">
<img alt="codecov" src="https://codecov.io/gh/unnoq/orpc/branch/main/graph/badge.svg">
</a>
<a href="https://www.npmjs.com/package/@orpc/experimental-ratelimit">
<img alt="weekly downloads" src="https://img.shields.io/npm/dw/%40orpc%2Fexperimental-ratelimit?logo=npm" />
</a>
<a href="https://github.com/unnoq/orpc/blob/main/LICENSE">
<img alt="MIT License" src="https://img.shields.io/github/license/unnoq/orpc?logo=open-source-initiative" />
</a>
<a href="https://discord.gg/TXEbwRBvQn">
<img alt="Discord" src="https://img.shields.io/discord/1308966753044398161?color=7389D8&label&logo=discord&logoColor=ffffff" />
</a>
<a href="https://deepwiki.com/unnoq/orpc">
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki">
</a>
</div>

<h3 align="center">Typesafe APIs Made Simple 🪄</h3>

**oRPC is a powerful combination of RPC and OpenAPI**, makes it easy to build APIs that are end-to-end type-safe and adhere to OpenAPI standards

---

## Highlights

- **🔗 End-to-End Type Safety**: Ensure type-safe inputs, outputs, and errors from client to server.
- **📘 First-Class OpenAPI**: Built-in support that fully adheres to the OpenAPI standard.
- **📝 Contract-First Development**: Optionally define your API contract before implementation.
- **🔍 First-Class OpenTelemetry**: Seamlessly integrate with OpenTelemetry for observability.
- **⚙️ Framework Integrations**: Seamlessly integrate with TanStack Query (React, Vue, Solid, Svelte, Angular), SWR, Pinia Colada, and more.
- **🚀 Server Actions**: Fully compatible with React Server Actions on Next.js, TanStack Start, and other platforms.
- **🔠 Standard Schema Support**: Works out of the box with Zod, Valibot, ArkType, and other schema validators.
- **🗃️ Native Types**: Supports native types like Date, File, Blob, BigInt, URL, and more.
- **⏱️ Lazy Router**: Enhance cold start times with our lazy routing feature.
- **📡 SSE & Streaming**: Enjoy full type-safe support for SSE and streaming.
- **🌍 Multi-Runtime Support**: Fast and lightweight on Cloudflare, Deno, Bun, Node.js, and beyond.
- **🔌 Extendability**: Easily extend functionality with plugins, middleware, and interceptors.

## Documentation

You can find the full documentation [here](https://orpc.unnoq.com).

## Packages

- [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Build your API contract.
- [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract.
- [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety.
- [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Generate OpenAPI specs and handle OpenAPI requests.
- [@orpc/otel](https://www.npmjs.com/package/@orpc/otel): [OpenTelemetry](https://opentelemetry.io/) integration for observability.
- [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with [NestJS](https://nestjs.com/).
- [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions.
- [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query): [TanStack Query](https://tanstack.com/query/latest) integration.
- [@orpc/experimental-react-swr](https://www.npmjs.com/package/@orpc/experimental-react-swr): [SWR](https://swr.vercel.app/) integration.
- [@orpc/vue-colada](https://www.npmjs.com/package/@orpc/vue-colada): Integration with [Pinia Colada](https://pinia-colada.esm.dev/).
- [@orpc/hey-api](https://www.npmjs.com/package/@orpc/hey-api): [Hey API](https://heyapi.dev/) integration.
- [@orpc/zod](https://www.npmjs.com/package/@orpc/zod): More schemas that [Zod](https://zod.dev/) doesn't support yet.
- [@orpc/valibot](https://www.npmjs.com/package/@orpc/valibot): OpenAPI spec generation from [Valibot](https://valibot.dev/).
- [@orpc/arktype](https://www.npmjs.com/package/@orpc/arktype): OpenAPI spec generation from [ArkType](https://arktype.io/).

## `@orpc/experimental-ratelimit`

Rate Limiting Feature for oRPC

## Sponsors

<p align="center">
<a href="https://cdn.jsdelivr.net/gh/unnoq/unnoq/sponsors.svg">
<img src='https://cdn.jsdelivr.net/gh/unnoq/unnoq/sponsors.svg'/>
</a>
</p>

## License

Distributed under the MIT License. See [LICENSE](https://github.com/unnoq/orpc/blob/main/LICENSE) for more information.
Loading
Loading