Maîtriser Redis de zéro : types de données, commandes essentielles, expiration, persistance, cas d'usage réels (cache, sessions, files d'attente) et intégration Node.js.
ioredis est le client Redis le plus utilisé pour Node.js. Support complet de l'API Redis, reconnexion automatique, pipelines, Lua scripts, cluster. La bibliothèque officielle (redis) est aussi valide, mais ioredis reste le standard en production.
npm install ioredisimport Redis from 'ioredis'
const redis = new Redis({
host: process.env.REDIS_HOST || '127.0.0.1',
port: Number(process.env.REDIS_PORT) || 6379,
password: process.env.REDIS_PASSWORD,
db: 0, // base de données (0-15)
lazyConnect: true, // ne se connecte qu'au premier usage
maxRetriesPerRequest: 3,
})
redis.on('error', (err) => console.error('Redis error:', err))
redis.on('connect', () => console.log('Redis connected'))
export default redisEn Next.js ou autre framework qui recharge les modules en dev, créer le client en singleton :
import Redis from 'ioredis'
declare global {
// eslint-disable-next-line no-var
var redis: Redis | undefined
}
const redis = global.redis ?? new Redis(process.env.REDIS_URL || 'redis://localhost:6379')
if (process.env.NODE_ENV !== 'production') {
global.redis = redis
}
export default redisimport redis from '@/lib/redis'
// String
await redis.set('key', 'value')
await redis.set('key', 'value', 'EX', 3600) // expire dans 1h
const val = await redis.get('key') // string | null
// Incrémenter
await redis.incr('counter')
await redis.incrby('counter', 5)
// Hash
await redis.hset('user:42', { name: 'Alice', role: 'admin' })
const name = await redis.hget('user:42', 'name')
const user = await redis.hgetall('user:42') // Record<string, string>
// List
await redis.lpush('queue', JSON.stringify({ task: 'send_email' }))
const task = await redis.rpop('queue') // string | null
// Set
await redis.sadd('tags', 'redis', 'nodejs')
const isMember = await redis.sismember('tags', 'redis') // 0 | 1
// Supprimer
await redis.del('key')
const ttl = await redis.ttl('key') // -2 si inexistant, -1 si pas d'expirationimport redis from './redis'
export async function cached<T>(
key: string,
ttl: number,
fn: () => Promise<T>
): Promise<T> {
const cached = await redis.get(key)
if (cached) return JSON.parse(cached) as T
const result = await fn()
await redis.set(key, JSON.stringify(result), 'EX', ttl)
return result
}Usage :
// Dans une route API Next.js
const articles = await cached(
'articles:featured',
300, // 5 minutes
() => db.query('SELECT * FROM articles WHERE featured = true')
)import redis from './redis'
import { randomBytes } from 'crypto'
const SESSION_TTL = 86400 // 24h
export async function createSession(userId: number, role: string): Promise<string> {
const sessionId = randomBytes(32).toString('hex')
await redis.set(
`session:${sessionId}`,
JSON.stringify({ userId, role, createdAt: Date.now() }),
'EX', SESSION_TTL
)
return sessionId
}
export async function getSession(sessionId: string) {
const data = await redis.get(`session:${sessionId}`)
if (!data) return null
// Renouveler le TTL (sliding expiration)
await redis.expire(`session:${sessionId}`, SESSION_TTL)
return JSON.parse(data)
}
export async function deleteSession(sessionId: string): Promise<void> {
await redis.del(`session:${sessionId}`)
}import redis from './redis'
export async function rateLimit(
identifier: string,
limit: number,
windowSeconds: number
): Promise<{ allowed: boolean; remaining: number }> {
const key = `ratelimit:${identifier}:${Math.floor(Date.now() / (windowSeconds * 1000))}`
const pipeline = redis.pipeline()
pipeline.incr(key)
pipeline.expire(key, windowSeconds)
const results = await pipeline.exec()
const count = (results?.[0]?.[1] as number) ?? 0
return {
allowed: count <= limit,
remaining: Math.max(0, limit - count),
}
}Usage dans un middleware Next.js :
import { rateLimit } from '@/lib/ratelimit'
import { NextRequest, NextResponse } from 'next/server'
export async function middleware(req: NextRequest) {
const ip = req.headers.get('x-forwarded-for') ?? 'unknown'
const { allowed, remaining } = await rateLimit(ip, 100, 3600)
if (!allowed) {
return new NextResponse('Too Many Requests', {
status: 429,
headers: { 'X-RateLimit-Remaining': '0' },
})
}
const res = NextResponse.next()
res.headers.set('X-RateLimit-Remaining', String(remaining))
return res
}Les pipelines envoient plusieurs commandes en une seule requête réseau, réduisant la latence de N×RTT à 1×RTT :
const pipeline = redis.pipeline()
pipeline.set('key1', 'val1')
pipeline.set('key2', 'val2')
pipeline.incr('counter')
pipeline.expire('counter', 3600)
const results = await pipeline.exec()
// results = [[null, 'OK'], [null, 'OK'], [null, 3], [null, 1]]
// [err, value] pour chaque commandeLe code est en place. Le dernier chapitre couvre le déploiement Docker sécurisé et le monitoring en production.