Skip to Content
Documentation

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

FeatureWithout RedisWith 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 othersShared by every instance, with the TTL you ask for
SessionsCached in process for up to 15 seconds, then read from the databaseCached in Redis for up to 60 seconds, then read from the database
Rate limiterCounters in memory, per instanceCounters shared by every instance
WebSocket realtimeDelivered only to clients on the same instanceDelivered 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:

docker-compose.yml
services:
  redis:
    image: redis:7-alpine
    restart: always
    ports:
      - "6379:6379"
    command: redis-server --requirepass root

In 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:

apps/api/.env
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=root

Pass them to the API config

The starter apps already contain this. Check that redis is set in vitnode.api.config.ts:

apps/api/src/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:

plugins/site-notes/src/api/cache/notes-total.cache.ts
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.