ichibaseichibase

Redis cache

A dedicated, per-project Redis instance for caching, ephemeral data, and rate-limit counters. Available on the Pro and Business plans. Treat it as a cache, not a database — keys are evicted when memory fills up and nothing is persisted to disk.

Enabling Redis

Redis is a paid feature. Free-plan projects share a single VPS where a per-project Redis container doesn't fit the resource budget, so the feature is gated to Pro and Business.

Turn it on from the project dashboard under Project → Redis. There you pick a memory size (Redis maxmemory) within your plan's range:

Plan        Memory range        Default
Pro         16 MB – 256 MB      64 MB
Business    16 MB – 1024 MB     256 MB

Eviction policy: allkeys-lru (oldest keys drop first when full)
Persistence:     none (in-memory only)
Resizing the memory cap recreates the Redis container, which drops all in-memory data. Because Redis is a cache this is expected — your code should always tolerate a cold cache.

Where Redis runs — server-side only

The @ichibase/redisSDK connects to your project's Redis over a raw connection URL of the form redis://:<password>@host:6379. That password is a privileged secret with full read/write access to your cache — it is not a scoped ich_pub_… client key and there is no per-key access control like RLS.

Never ship the Redis URL or password to a browser or native client. Anyone who holds it can read and overwrite every key in your cache. Use Redis only from a server you control or from an Edge Function, where the connection URL is injected for you as ICHIBASE_REDIS_URLand never leaves the server. To let a client touch the cache, front it with an Edge Function that validates the caller and performs the Redis operation on their behalf — see Edge Functions.

Inside an Edge Function you don't pass any connection details: createRedis() reads ICHIBASE_REDIS_URL from the environment automatically. Elsewhere (your own server, a worker, a script) pass the URL or host/port/password explicitly.

Connecting

import { createRedis } from 'jsr:@ichibase/redis';

// Inside an Edge Function: ICHIBASE_REDIS_URL is set for you.
const redis = await createRedis();

// Outside the platform: pass the URL (or host/port/password) yourself.
const redis2 = await createRedis({
  url: Deno.env.get('ICHIBASE_REDIS_URL'),
});

// Long-lived workers: keep one client; call redis.close() on shutdown.

Reading and writing

createRedis() returns a standard Redis client (the @db/redisdriver), so you get the familiar command set. The examples below use the commands you'll reach for most.

const redis = await createRedis();

// Strings
await redis.set('greeting', 'hello');
const greeting = await redis.get('greeting'); // "hello"

// Set with a TTL (seconds) — value auto-expires
await redis.set('otp:42', '123456', { ex: 300 }); // 5 minutes

// Counters
await redis.incr('views:home');           // 1, 2, 3, …
await redis.incrby('views:home', 10);     // jump by 10

// Expiry on an existing key, and time-to-live lookup
await redis.expire('session:abc', 3600);  // 1 hour
const ttl = await redis.ttl('session:abc'); // seconds left

// Delete
await redis.del('greeting');

// Done — close in short-lived contexts
await redis.close();

Pattern: cache-aside

The most common pattern. Check the cache first; on a miss, read from the source of truth, then store the result with a TTL so it refreshes on its own.

// Inside an Edge Function
import { createIchibase } from 'jsr:@ichibase/edge';
import { createRedis } from 'jsr:@ichibase/redis';

const ichi = createIchibase();
const redis = await createRedis();

async function getProfile(id: string) {
  const cacheKey = `profile:${id}`;

  // 1. Try the cache
  const hit = await redis.get(cacheKey);
  if (hit) return JSON.parse(hit);

  // 2. Miss — read from the database
  const { data } = await ichi.from('profiles').select('*').eq('id', id).single();

  // 3. Populate the cache for next time (10-minute TTL)
  if (data) await redis.set(cacheKey, JSON.stringify(data), { ex: 600 });

  return data;
}

Pattern: rate-limit counter

Increment a per-caller key and set its expiry on the first hit. When the count crosses your threshold inside the window, reject the request.

async function allow(ip: string, limit = 60, windowSec = 60) {
  const key = `rl:${ip}`;
  const count = await redis.incr(key);
  if (count === 1) {
    // First request in this window — start the clock
    await redis.expire(key, windowSec);
  }
  return count <= limit;
}

if (!(await allow(clientIp))) {
  return new Response('Too Many Requests', { status: 429 });
}
Because the key carries a TTL, the window resets on its own and the key eventually disappears — no cleanup job needed.

Keep in mind

Redis here is ephemeral. With the allkeys-lrueviction policy, when the cache reaches its memory cap Redis drops the least-recently-used keys to make room — even keys that haven't expired yet. There is no disk persistence, so a container restart or a memory resize clears everything.

Design for it: always set a TTL where it makes sense, never treat Redis as the only copy of important data, and make sure your code works when a key isn't there.