Skip to content

Define and use application-layer rate limits in Convex. Type-safe, transactional, fair, safe, and configurable sharding to scale.

License

Notifications You must be signed in to change notification settings

get-convex/ratelimiter

Repository files navigation

Convex Rate Limiter Component

npm version

Note: Convex Components are currently in beta

Application-level rate limiting.

  • Type-safe
  • Configurable for fixed window or token bucket algorithms
  • Efficient storage and compute
  • Configurable sharding for scalability
  • Transactional evaluation
  • Fairness guarantees via credit "reservation"
  • Opt-in "rollover" or "burst" allowance via "token bucket" config
  • Fails closed, not open
const rateLimiter = new RateLimiter(components.ratelimiter, {
  // One global / singleton rate limit, using a "fixed window" algorithm.
  freeTrialSignUp: { kind: "fixed window", rate: 100, period: HOUR },
  // A per-user limit, allowing one every ~6 seconds.
  // Allows up to 3 in quick succession if they haven't sent many recently.
  sendMessage: { kind: "token bucket", rate: 10, period: MINUTE, capacity: 3 },
  failedLogins: { kind: "token bucket", rate: 10, period: HOUR },
  // Use sharding to increase throughput without compromising on correctness.
  llmTokens: { kind: "token bucket", rate: 40000, period: MINUTE, shards: 100 },
  llmRequests: { kind: "fixed window", rate: 3, period: MINUTE },
});
// Restrict how fast free users can sign up to deter bots
const status = await rateLimiter.limit(ctx, "freeTrialSignUp");

// Limit how fast a user can send messages
const status = await rateLimiter.limit(ctx, "sendMessage", { key: userId });

// Automatically throw an error if the rate limit is hit
await rateLimiter.limit(ctx, "failedLogins", { key: userId, throws: true });

// Consume multiple in one request to prevent rate limits on an LLM API.
const status = await rateLimiter.limit(ctx, "llmTokens", { count: tokens });

// Reserve future capacity instead of just failing now
if (!args.skipCheck) {
  const status = await rateLimiter.limit(ctx, "llmRequests", { reserve: true });
  if (status.retryAfter) {
    return ctx.scheduler.runAfter(status.retryAfter, internal.foo.bar, {
      skipCheck: true,
    });
  }
}

// Check a rate limit without consuming it
const status = await rateLimiter.check(ctx, "failedLogins", { key: userId });

// Reset a rate limit on successful login
await rateLimiter.reset(ctx, "failedLogins", { key: userId });

// Use a one-off rate limit config (when not named on initialization)
const config = { kind: "fixed window", rate: 1, period: SECOND };
await rateLimiter.limit(ctx, "oneOffName", { config, throws: true });

See this article for more information.

Convex App

You'll need a Convex App to use the component. Follow any of the Convex quickstarts to set one up.

Installation

Install the component package:

npm install @convex-dev/ratelimiter

Create a convex.config.ts file in your app's convex/ folder and install the component by calling use:

// convex/convex.config.ts
import { defineApp } from "convex/server";
import ratelimiter from "@convex-dev/ratelimiter/convex.config";

const app = defineApp();
app.use(ratelimiter);

export default app;

Usage

Define your rate limits:

import { RateLimiter } from "@convex-dev/ratelimiter";
import { components } from "./_generated/api";

const rateLimiter = new RateLimiter(components.ratelimiter, {
  // One global / singleton rate limit
  freeTrialSignUp: { kind: "fixed window", rate: 100, period: HOUR },
  sendMessage: { kind: "token bucket", rate: 10, period: MINUTE, capacity: 3 },
});
  • You can safely generate multiple instances if you want to define different rates in separate places.

Use it from a mutation or action:

const { ok, retryAfter } = await rateLimiter.limit(ctx, "freeTrialSignUp");

Or if you want to rate limit based on a key:

await rateLimiter.limit(ctx, "sendMessage", { key: user._id, throws: true });

This call also throws an exception, so you don't have to check the return value.

Check out a full example here.

See this article for more information on usage and advanced patterns.

🧑‍🏫 What is Convex?

Convex is a hosted backend platform with a built-in database that lets you write your database schema and server functions in TypeScript. Server-side database queries automatically cache and subscribe to data, powering a realtime useQuery hook in our React client. There are also clients for Python, Rust, ReactNative, and Node, as well as a straightforward HTTP API.

The database supports NoSQL-style documents with opt-in schema validation, relationships and custom indexes (including on fields in nested objects).

The query and mutation server functions have transactional, low latency access to the database and leverage our v8 runtime with determinism guardrails to provide the strongest ACID guarantees on the market: immediate consistency, serializable isolation, and automatic conflict resolution via optimistic multi-version concurrency control (OCC / MVCC).

The action server functions have access to external APIs and enable other side-effects and non-determinism in either our optimized v8 runtime or a more flexible node runtime.

Functions can run in the background via scheduling and cron jobs.

Development is cloud-first, with hot reloads for server function editing via the CLI, preview deployments, logging and exception reporting integrations, There is a dashboard UI to browse and edit data, edit environment variables, view logs, run server functions, and more.

There are built-in features for reactive pagination, file storage, reactive text search, vector search, https endpoints (for webhooks), snapshot import/export, streaming import/export, and runtime validation for function arguments and database data.

Everything scales automatically, and it’s free to start.

About

Define and use application-layer rate limits in Convex. Type-safe, transactional, fair, safe, and configurable sharding to scale.

Resources

License

Stars

Watchers

Forks

Packages

No packages published