Skip to content

Redis

Postgres keeps what is true. Redis keeps what every copy of the API has to share while it runs:

What Read more
The job queue, with BullMQ Background jobs
Events to everyone connected to a workspace Realtime
The assistant’s answers while they are written AI features
Rate limits: signing in, API keys, and your own below
A cache for your own features below

Losing what is in Redis loses no data: queued jobs are in Postgres until BullMQ has them, an event is a nudge to look again, and an answer can be asked for again. Back it up anyway, for the jobs BullMQ is holding: Railway’s Redis keeps its data on a volume.

bun run services:up starts it with Postgres and Mailpit; REDIS_URL points at it. On Railway it is a service of the project, and the API reads its REDIS_URL: see Railway. On another host, Redis 7 or later (the kit is tested on 8), or Valkey. /api/health and bun run deploy:check say when the API cannot reach it.

Every key the app writes starts with app:, through RedisService.key(). Use it for keys of your own, so they never meet BullMQ’s or the kit’s.

RateLimiter counts in Redis, so a limit holds however many copies of the API run:

const { allowed, retryAfter } = await this.limits.consume(`export:${workspace.id}`, {
max: 10,
window: 60,
});
if (!allowed) throw new HttpException(`Try again in ${retryAfter} seconds.`, 429);

Better Auth’s limits on signing in go through it as well, in production, as they did in memory before: see Auth.

CacheService caches for one workspace at a time, so one workspace’s entries can never be read through another’s:

const cache = this.cache.forWorkspace(workspace.id);
const report = await cache.wrap('report:2026-10', 300, () => this.buildReport(workspace.id));
await cache.delete('report:2026-10');

Values are kept as JSON for the seconds you give. clear() forgets a workspace’s entries. The kit caches nothing of its own: reach for it when something is slow and may be a little out of date.