Introduction
Benni is the end-to-end typed Redis client for TypeScript: one typed API across Node, Bun, Deno, and the edge (Upstash/HTTP).
node-redis and ioredis are already typed, but only at the command surface: a reply comes back as Redis’s generic wire shape (string | null, Record<string, string>), so your type is gone the moment data crosses the Redis edge and you re-parse it by hand. Benni declares your data model once with typed codecs and carries those types from write to read. They type the commands; Benni types your data.
import { hash, number, string } from "benni/schema";
export const users = hash("user", { name: string(), score: number()});
await redis.hash(users).hset("42", { name: "Ada", score: 10});
const user = await redis.hash(users).hget("42");// ^? { name: string; score: number } | nullBenni is a typed client, not an ORM. It does not create tables, run migrations, or hide Redis. A Benni schema is a plain TypeScript description of a Redis key family, and raw access is always one call away.
The mental model is small:
Schema = how a Redis key family is shapedClient = typed Redis access bound to your schemasRaw = direct Redis access when that is clearerUse Benni where schemas help. Use raw Redis where Redis itself is the clearest API.