A TypeScript-first Redis data layer for Node.js with normalization, atomic concurrent writes, ZSET-backed paginated lists, automatic relationship hydration, and built-in resilience.
import { RedisGraphCache } from 'redis-graph-cache';
const schema = {
post: {
type: 'entity' as const,
id: 'id',
key: (id) => `post:${id}`,
fields: {
title: { type: 'string' as const },
content: { type: 'string' as const },
published: { type: 'boolean' as const },
},
relations: {
author: { type: 'user', kind: 'one' as const },
category: { type: 'category', kind: 'one' as const },
},
ttl: 3600,
},
user: {
type: 'entity' as const,
id: 'id',
key: (id) => `user:${id}`,
fields: {
name: { type: 'string' as const },
email: { type: 'string' as const },
},
ttl: 7200,
},
category: {
type: 'entity' as const,
id: 'id',
key: (id) => `category:${id}`,
fields: {
name: { type: 'string' as const },
slug: { type: 'string' as const },
},
ttl: 86400,
},
};
const cache = new RedisGraphCache(schema, {
redis: { host: 'localhost', port: 6379 },
});
// Write ONE nested object...
await cache.writeEntity('post', {
id: 1,
title: 'Hello World',
content: 'Graph caching for Redis.',
published: true,
author: { id: 9, name: 'Ada', email: 'ada@example.com' },
category: { id: 3, name: 'Tech', slug: 'tech' },
});
// ...three keys land in Redis, each with its own TTL.
// Reads recompose the graph:
const post = await cache.readEntity('post', 1);
// post.author.name === 'Ada'
// post.category.slug === 'tech'# One write produced three normalized keys.
# Nested objects became id references — not duplicated blobs.
127.0.0.1:6379> KEYS *
1) "post:1"
2) "user:9"
3) "category:3"
127.0.0.1:6379> GET post:1
{"id":1,"title":"Hello World",
"content":"Graph caching for Redis.",
"published":true,
"__rse_authorId":9,
"__rse_categoryId":3}
127.0.0.1:6379> GET user:9
{"id":9,"name":"Ada","email":"ada@example.com"}
127.0.0.1:6379> GET category:3
{"id":3,"name":"Tech","slug":"tech"}
# Each key carries its own TTL from the schema.
127.0.0.1:6379> TTL post:1 → 3600
127.0.0.1:6379> TTL user:9 → 7200
127.0.0.1:6379> TTL category:3 → 86400
# readEntity('post', 1) rehydrates the full graph
# — no duplicated data, no stale copies, no N+1 round trips.Caching nested objects in Redis is harder than it looks. We solve the hard problems so you don't have to.
Declare entities and relationships once. Auto-validates at boot and handles normalization, hydration, and key generation.
Every read-modify-write path is implemented as a single Lua script. No race conditions, ever.
One call removes an entity from all lists tracking it. Automatic relationship management.
Paginated reads, sorting by any field, atomic add/remove, size caps, and built-in pagination.
Full generic typing with keyof TSchema everywhere. Type-safe API from schema definition.
Circuit breaker, exponential backoff retries, real metrics, and production safety guards.
One command. That's all it takes.
npm add redis-graph-cache ioredisyarn add redis-graph-cache ioredispnpm add redis-graph-cache ioredis