Skip to main content
UpstashRedisCache is the Cache component’s serverless backend. It talks to Upstash Redis over its HTTP REST API — no persistent TCP connection — which makes it a natural fit for edge and serverless runtimes. It implements the same ICache interface as every other backend, so you can develop against the filesystem or a local Redis and switch to Upstash in production without changing a single call site.

Why Upstash

  • Serverless-friendly. Connects over HTTP REST, so it works in edge functions and short-lived runtimes where TCP pooling is awkward.
  • Same ICache interface. get, set, delete, has, deleteByPrefix, and clear — identical to the filesystem and Redis backends.
  • Namespaced keys. Every key is prefixed with a namespace (default cache) so multiple apps can share one Upstash database safely.
  • Prefix operations. deleteByPrefix() and clear() use cursor-based SCAN plus pipelined deletes to stay efficient on large keyspaces.
  • Container-managed. Registered with @decorator.cache() and resolved from the container.

Installation

UpstashRedisCache ships with @ooneex/cache and depends on the Upstash Redis client.
bun add @ooneex/cache @upstash/redis

Environment variables

VariableRequiredPurpose
CACHE_UPSTASH_REDIS_REST_URLYesUpstash Redis REST URL. Missing throws CacheException (CONFIG_REQUIRED).
CACHE_UPSTASH_REDIS_REST_TOKENYesUpstash Redis REST token. Missing throws CacheException (CONFIG_REQUIRED).
CACHE_UPSTASH_REDIS_REST_URL=https://your-db.upstash.io
CACHE_UPSTASH_REDIS_REST_TOKEN=your_rest_token
Both can also be passed through constructor options (url, token), which take precedence over the environment. Either way they are validated when the cache is constructed, so misconfiguration fails fast.

Options

UpstashRedisCache accepts an options object as its second constructor argument:
OptionTypeDefaultPurpose
namespacestring"cache"Key prefix; every key is stored as <namespace>:<key>.
urlstringCACHE_UPSTASH_REDIS_REST_URLOverride the REST URL.
tokenstringCACHE_UPSTASH_REDIS_REST_TOKENOverride the REST token.

How it works

Keys are namespaced before they hit Redis: set("user:1", …) with the default namespace writes cache:user:1. Reads, writes, and existence checks map straight onto Upstash commands, while the bulk operations iterate the keyspace with SCAN and delete matches in a pipeline.
MethodPurpose
get<T>(key)Read a value, or undefined if absent.
set<T>(key, value, ttl?)Write a value; ttl (seconds) sets an expiry via EX.
delete(key)Delete one key; returns whether a key was removed.
has(key)Whether a key exists.
deleteByPrefix(prefix)SCAN + pipelined delete of <namespace>:<prefix>*; returns the count deleted.
clear()SCAN + pipelined delete of every key under the namespace.

Usage

The API is identical to every other cache backend.
import { container } from "@ooneex/container";
import { UpstashRedisCache } from "@ooneex/cache";

const cache = container.get(UpstashRedisCache);

// Write with a 5-minute TTL
await cache.set("user:123", { id: 123, name: "Ada" }, 300);

// Read it back
const user = await cache.get<{ id: number; name: string }>("user:123");

// Existence and removal
await cache.has("user:123"); // true
await cache.delete("user:123"); // true

// Bulk operations
await cache.deleteByPrefix("user:"); // delete every user:* key, returns the count
await cache.clear(); // empty the whole namespace
To run several isolated caches against one Upstash database, give each its own namespace:
import { decorator } from "@ooneex/cache";
import { UpstashRedisCache } from "@ooneex/cache";
import { AppEnv } from "@ooneex/app-env";
import { inject } from "@ooneex/container";

@decorator.cache()
export class SessionCache extends UpstashRedisCache {
  constructor(@inject(AppEnv) env: AppEnv) {
    super(env, { namespace: "sessions" });
  }
}

Use in the app

In an @ooneex/app application, pass UpstashRedisCache to the cache slot of your App config. The framework registers it and exposes it as the "cache" container constant.
bun add @ooneex/app @ooneex/cache @upstash/redis
import { App } from "@ooneex/app";
import { UpstashRedisCache } from "@ooneex/cache";
import { TerminalLogger } from "@ooneex/logger";

const app = new App({
  routing: { prefix: "/api" },
  loggers: [TerminalLogger],
  cache: UpstashRedisCache,
});

await app.run();
With a cache configured, any route that declares a cache key has its response cached automatically — the framework builds the cache key from the route’s cache prefix, HTTP method, URL, and the authenticated user’s id:
import { Route } from "@ooneex/routing";

@Route.get("/api/products", {
  name: "products",
  version: 1,
  description: "List products",
  cache: "products", // response cached through the configured UpstashRedisCache
})
export class ProductsController {
  public async index(context: ContextType<ProductsRouteType>) {
    return context.response.json(await this.products.findAll());
  }
}
You can also reach the same instance anywhere for manual caching — inject it or read the constant:
import { container } from "@ooneex/container";
import type { ICache } from "@ooneex/cache";

const cache = container.getConstant<ICache>("cache"); // the app's configured cache
Set CACHE_UPSTASH_REDIS_REST_URL and CACHE_UPSTASH_REDIS_REST_TOKEN in your .env.yml (or environment) so the cache can connect.

Exceptions

UpstashRedisCache throws CacheException on misconfiguration, carrying a machine-readable key.
KeyWhen
CONFIG_REQUIREDThe REST URL or token is missing from both options and the environment.
import { CacheException } from "@ooneex/cache";

try {
  const cache = container.get(UpstashRedisCache);
} catch (error) {
  if (error instanceof CacheException && error.key === "CONFIG_REQUIRED") {
    logger.error("Upstash cache is misconfigured", error.data);
  }
  throw error;
}

Best practices

  • Set a TTL for volatile data. Pass ttl (seconds) on set() so transient values expire instead of accumulating.
  • Namespace per concern. Use distinct namespaces (sessions, rate counters, query cache) so clear() and deleteByPrefix() only touch what you intend.
  • Use structured key prefixes. Group related keys like user:123:profile so deleteByPrefix("user:123:") can invalidate them together.
  • Keep credentials in the environment. Load the REST URL and token from .env; never hard-code them.
  • Develop locally, deploy on Upstash. Use the filesystem or local Redis backend in development and switch to Upstash in production — the ICache calls stay identical.
See the Cache component for the full interface and the other backends.