Upstash
Use Upstash Redis for the MainSDK key-value store, cache and rate limits - setup, main.yaml, secrets, local testing and swapping.
Upstash is serverless Redis reached over HTTP, and a driver behind two interfaces: kv, for small values and cached results, and limits, for rate limits, locks and quotas. Because it needs no long-lived connection, it fits serverless and edge functions where a classic Redis connection pool does not. Pick Upstash when you deploy on a serverless platform and want one store for your cache and your counters.
Interfaces it serves
Pick Upstash in main.yaml for any of these. Your calls stay the same, and you can swap later.
kvBuilding for 0.3Driver upstash Upstash Redis
planned; reach it today with the redis driver and a rediss:// URL
kv:
driver: upstashSwap to another provider
Before you start
You need an Upstash account.
- Create a Redis database. Choose a primary region close to where your app runs. Upstash can also replicate reads to more regions; use that only for data that may be copied to every region it lists.
- Copy the REST URL and token. Upstash shows them in the database's details. The token grants read and write access, so treat it as a secret.
- Decide on one database or two. You can share one database between
kvandlimits, or keep counters apart from cached data so a cache flush never resets a rate limit.
Store the URL and token in your secrets manager, never in code or a committed env file.
Configure
For the cache and key-value store:
kv:
driver: upstash
# token: secret:UPSTASH_REDIS_REST_TOKEN # suggested name; key names are [to be decided] until the kv specFor rate limits, locks and quotas:
limits:
driver: upstash # was builtinHow the limits driver gets its URL and token, and whether it can share the kv connection, are not decided yet. There is no default TTL for kv: you pass ttl on every write.
Use it
Cache a slow lookup:
import { cache } from "mainsdk";
export async function pricesFor(market: string) {
return cache.wrap(`prices:${market}`, () => loadPrices(market), {
ttl: "5m",
tags: ["prices"],
});
}Rate-limit an expensive endpoint:
import { auth, limits } from "mainsdk";
export async function POST(req: Request) {
const session = await auth.require(req);
const ok = await limits.rate(`export:${session.user.id}`, { per: "1m", max: 5 });
if (!ok) return new Response("Too many requests", { status: 429 });
return Response.json({ started: true });
}limits.lock(name, fn) runs fn on one worker at a time, and limits.quota(org, metric) reports where an organisation stands against its plan.
Local development and testing
- No account: tests don't need Upstash.
limitsdefaults to thebuiltindriver, so you can set Upstash inmain.env.prod.yamlonly. The local driver forkvis not named yet. - A real database: a separate Upstash database for development keeps test counters and cache out of production.
- Tests:
createTestApp()from@mainsdk/testingrunskvandlimitson in-memory fakes. Useclock.advance()to pass a TTL or reset a rate window, andchaos.on("kv", { outage: true })orchaos.on("limits", { outage: true })to simulate an Upstash outage.
import { it } from "node:test";
import assert from "node:assert/strict";
import { createTestApp, clock } from "@mainsdk/testing";
import { limits } from "mainsdk";
it("refuses the sixth export in a minute and allows it after", async () => {
createTestApp({ main: "./main.yaml" });
const rule = { per: "1m", max: 5 };
for (let i = 0; i < 5; i++) assert.equal(await limits.rate("export:u1", rule), true);
assert.equal(await limits.rate("export:u1", rule), false);
clock.advance("1m");
assert.equal(await limits.rate("export:u1", rule), true);
});What MainSDK adds on top
kvkeys scoped per tenant, a TTL on every entry, and no personal data in shared caches without a label.- Sign-in, sign-up, password reset and AI calls rate limited by default, whichever
limitsdriver you pick. - Abuse and bot protection as part of
limits. - The same calls on every driver, so you can move counters between the built-in driver and Upstash with one line.
See Key-value store and cache and Rate limits and quotas.
Limits and notes
upstashis a planned driver forkvandlimits, both phase 1, and neither interface has a spec yet.- Pick database regions that match your data rules. Cached values that hold personal data follow the same residency rules as the records they came from (US, EU or India); see Data and regions.
- Limit by user or organisation ID rather than by an email address, so personal data stays out of counter keys.
- Use
db, notkv, for data you must keep.
Swap to or from it
kv:
driver: upstash # was redis
limits:
driver: upstash # was builtinCached entries refill as they expire, and rate-limit counters start fresh on the new driver. No migration is needed.
FAQ
Upstash or plain Redis? Upstash suits serverless functions because it is reached over HTTP. Use Redis when you already run a server in your own network.
Do I need Upstash for rate limits? No. limits works on its builtin driver. Move to Upstash when you want counters shared across many instances in a store you control.
Can one Upstash database serve both kv and limits? Whether the two drivers share one connection is not decided yet; two databases always work.
Next
Open questions
Was this page helpful?