Advanced
Redis
Connect Redis to VitNode to share the API cache, sessions, rate-limit counters and realtime events across instances, and see what happens without it.
Redis is VitNode's shared cache and pub/sub broker. It is optional. On a single API instance VitNode works without it and uses an in-process cache and per-instance counters. Add Redis when you run more than one API instance, deploy to a serverless host, or want cached values to live longer than 15 seconds.
What Redis changes
| Feature | Without Redis | With Redis |
|---|---|---|
API cache (c.get("cache")) | In-process memory, TTLs capped at 15 seconds, per instance; a delete on one instance does not reach the others | Shared by every instance, with the TTL you ask for |
| Sessions | Cached in process for up to 15 seconds, then read from the database | Cached in Redis for up to 60 seconds, then read from the database |
| Rate limiter | Counters in memory, per instance | Counters shared by every instance |
| WebSocket realtime | Delivered only to clients on the same instance | Delivered across instances through Redis pub/sub; required on serverless hosts (details) |
Connect Redis
Start a Redis server
For local development, run Redis with Docker Compose. --requirepass sets the password used in the next step:
services:
redis:
image: redis:7-alpine
restart: always
ports:
- "6379:6379"
command: redis-server --requirepass rootIn production, use your host's managed Redis or your own server.
Set the environment variables
Add the connection URL and the password to the API's .env. The password is a separate variable, so the URL can stay free of secrets:
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=rootPass them to the API config
The starter apps already contain this. Check that redis is set in vitnode.api.config.ts:
import { buildApiConfig } from "@vitnode/core/vitnode.config";
export const vitNodeApiConfig = buildApiConfig({
redis: process.env.REDIS_URL
? { url: process.env.REDIS_URL, password: process.env.REDIS_PASSWORD }
: undefined,
});redis accepts any option of node-redis createClient. When REDIS_URL is unset, redis is undefined and VitNode uses the in-process fallbacks from the table above. Restart the API after changing .env.
Check the result
Open Debug Panel ā Cache in the AdminCP (/admin/core/debug/cache). With Redis connected you see Redis connected and whether realtime events reach every instance (Realtime across instances). Without Redis it says Redis is not configured, so the cache lives in memory. An unreachable server shows Redis is unreachable, also on the Debug Panel's Overview.
Use the cache in an API route
c.get("cache") works the same with or without Redis. Declare a cache entry with defineCacheEntry, then remember calls the loader only when the entry is missing:
import { z } from "zod";
import { defineCacheEntry } from "@/api/builders";
export const notesTotalCache = defineCacheEntry({
name: "notes-total",
version: 1,
schema: z.number().int().nonnegative(),
ttlSeconds: 60,
});import { count } from "drizzle-orm";
import { notesTotalCache } from "@/api/cache/notes-total.cache";
import { site_notes } from "@/database/notes";
// inside a route handler, where `c` is the Hono context
const total = await c
.get("cache")
.entry(notesTotalCache)
.remember(async () => {
const [row] = await c.get("db").select({ value: count() }).from(site_notes);
return row?.value ?? 0;
});Without Redis the 60-second TTL is capped at 15 seconds. Cache API data covers keys, invalidation after a write and checking the result.