Design and implement Redis data storage for a new feature. Use when adding new data models, leaderboards, counters, or any persistent state.
All code must follow the Coding Principles in AGENTS.md (functional, minimal, readable, modular).
{entity}:{identifier}:{attribute}
| Use case | Pattern | Example |
|---|---|---|
| Game state | game:{postId}:state | game:t3_abc:state |
| User stats | user:{userId}:stats | user:t2_xyz:stats |
| Per-game user data | user:{userId}:game:{postId} |
user:t2_xyz:game:t3_abc| Leaderboard | leaderboard:{scope}:{timeframe} | leaderboard:wins:daily |
| Global counter | stats:{metric} | stats:totalGames |
import { redis } from '@devvit/web/server'
// String (simple values, flags)
await redis.set('key', 'value')
const value = await redis.get('key') // string | null
// Numbers
await redis.incrBy('counter', 1)
// Hash (object/record)
await redis.hSet('user:t2_abc:stats', { solved: '5', bestTime: '120' })
const stats = await redis.hGetAll('user:t2_abc:stats') // Record<string, string>
// Sorted set (leaderboard)
await redis.zAdd('leaderboard:wins', { member: 'alice', score: 100 })
const top10 = await redis.zRange('leaderboard:wins', 0, 9, { by: 'score', reverse: true })
// Expiration
await redis.expire('session:abc', 3600) // seconds
// Batch reads (prefer over multiple round trips)
const [a, b] = await redis.mGet(['key1', 'key2'])
hGetAll over multiple hGet callsmGet over multiple get callsconst value = await redis.get('key')
if (value === null) {
// handle missing key — don't assume it exists
}
const stats = await redis.hGetAll('user:t2_abc:stats')
const solved = stats['solved'] // string | undefined — must check
if (solved !== undefined) {
const count = parseInt(solved, 10)
}
__tests__/ using bun:test and devvit-mocks for Redis operations{entity}:{id}:{field} patternparseInt/parseFloatget / hGetbun run test passes with zero failures