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.
You'll need a Convex App to use the component. Follow any of the Convex quickstarts to set one up.
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;
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.
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.