upstash-redis

Par github · awesome-copilot

Utilisez Redis via HTTP depuis des runtimes serverless et edge avec `@upstash/redis`, et ajoutez du rate limiting avec `@upstash/ratelimit`. À utiliser quand l'utilisateur mentionne Upstash Redis, a besoin de Redis depuis un route handler ou un middleware Next.js, Vercel, Cloudflare Workers, Deno ou Bun sans connection pooling TCP, ou souhaite un cache-aside avec des TTL, un session store, des compteurs, ou un rate limiter 429 avec fenêtre fixe, fenêtre glissante ou token bucket. NE PAS utiliser pour des clients Redis auto-hébergés ou TCP (`ioredis`, `node-redis`), l'administration de Redis Cluster, ou la recherche de similarité vectorielle.

npx skills add https://github.com/github/awesome-copilot --skill upstash-redis

Skill Upstash Redis

Ce skill couvre les trois cas d'usage pour lesquels les apps serverless ont le plus souvent besoin de Redis : le caching, les sessions et le rate limiting. Le client communique avec Redis via HTTP, ce qui fonctionne là où une connexion TCP longue durée ne fonctionne pas (edge middleware, functions éphémères). Suivez les étapes dans l'ordre ; chacune se termine par un checkpoint.

Requirements et limitations

  • Une base de données Upstash Redis (service hébergé ; tarification à l'usage avec un tier gratuit). Les credentials sont une URL REST et un token depuis la page de la base.
  • Variables d'environnement UPSTASH_REDIS_REST_URL et UPSTASH_REDIS_REST_TOKEN.
  • Chaque commande est une requête HTTP. Regroupez avec pipeline() ou MGET/MSET quand vous émettez beaucoup de commandes par requête ; évitez KEYS * en production.
  • Les valeurs sont sérialisées automatiquement (objets, tableaux, nombres conservent leurs types). Ne faites pas de JSON.stringify avant set ou de parseInt après get.

Step 1 — Installer et créer un client par module

npm install @upstash/redis @upstash/ratelimit
// lib/redis.ts
import { Redis } from "@upstash/redis";

// Lit UPSTASH_REDIS_REST_URL et UPSTASH_REDIS_REST_TOKEN
export const redis = Redis.fromEnv();

Créez le client à la portée du module, pas à l'intérieur du request handler, pour que les caches éphémères et les pipelines puissent être réutilisés entre les invocations.

Checkpoint : await redis.ping() retourne "PONG".

Step 2 — Cache-aside avec TTL

import { redis } from "@/lib/redis";

type User = { id: string; name: string; plan: "free" | "pro" };

export async function getUser(userId: string): Promise<User | null> {
  const key = `user:${userId}`;
  const cached = await redis.get<User>(key);
  if (cached) return cached;

  const user = await db.users.findById(userId); // votre source de données
  if (user) await redis.set(key, user, { ex: 3600 }); // TTL 1 heure
  return user;
}

export async function updateUser(userId: string, patch: Partial<User>) {
  const user = await db.users.update(userId, patch);
  await redis.set(`user:${userId}`, user, { ex: 3600 }); // write-through
  return user;
}

export async function deleteUser(userId: string) {
  await db.users.delete(userId);
  await redis.del(`user:${userId}`); // invalider
}

Définissez toujours un TTL sur les entrées du cache ; utilisez des namespaces de clés (user:123, session:abc).

Checkpoint : le deuxième appel à getUser retourne sans accéder à la base et await redis.ttl("user:123") est positif.

Step 3 — Sessions avec expiration glissante

import { redis } from "@/lib/redis";

const SESSION_TTL = 60 * 60 * 24; // 24 heures

export async function createSession(userId: string, data: Record<string, unknown>) {
  const sessionId = crypto.randomUUID();
  await redis.set(`session:${sessionId}`, { userId, ...data, createdAt: Date.now() }, { ex: SESSION_TTL });
  return sessionId;
}

export async function getSession<T = Record<string, unknown>>(sessionId: string) {
  const session = await redis.get<T>(`session:${sessionId}`);
  if (session) await redis.expire(`session:${sessionId}`, SESSION_TTL); // glisser
  return session;
}

export async function destroySession(sessionId: string) {
  await redis.del(`session:${sessionId}`);
}

Stockez l'id de session dans un cookie HttpOnly; Secure; SameSite ; ne mettez jamais le token Redis dans le code client.

Checkpoint : getSession après createSession retourne l'objet avec userId ; après destroySession il retourne null.

Step 4 — Rate limiting un route handler

// app/api/search/route.ts (Next.js App Router ; même pattern pour n'importe quel fetch handler)
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10 s"), // 10 requêtes par 10 secondes
  prefix: "ratelimit:search", // isoler les clés par limiter
});

export async function POST(request: Request) {
  const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "anonymous";
  const { success, limit, remaining, reset } = await ratelimit.limit(ip);

  if (!success) {
    return new Response("Too Many Requests", {
      status: 429,
      headers: {
        "X-RateLimit-Limit": String(limit),
        "X-RateLimit-Remaining": String(remaining),
        "Retry-After": String(Math.max(0, Math.ceil((reset - Date.now()) / 1000))),
      },
    });
  }

  // traiter la requête
  return Response.json({ ok: true });
}
  • Identifiant : utilisez l'id utilisateur ou la clé API quand authentifié ; sinon, l'IP.
  • Algorithmes : Ratelimit.fixedWindow(n, "1 m") (moins cher), slidingWindow (frontières lisses, choix par défaut), tokenBucket(refill, "10 s", max) (permet les pics). Les fenêtres acceptent ms, s, m, h, d.
  • Tiers : créez un Ratelimit par tier avec différentes valeurs prefix.
  • Edge middleware / Cloudflare Workers avec analytics: true : le résultat a une promesse pending ; passez-la à context.waitUntil(pending) pour que le travail de fond se termine avant la sortie du runtime.
  • reset est un timestamp Unix en millisecondes.

Checkpoint : la 11e requête dans 10 secondes retourne 429 avec un en-tête Retry-After ; après la fenêtre, elle réussit à nouveau.

Pièges courants

  • Créer les clients à l'intérieur des handlers : l'ephemeralCache en mémoire du limiter n'aide que si l'instance survit à la requête.
  • JSON manuel : redis.set("k", JSON.stringify(v)) puis redis.get retourne déjà un objet parsé ; le double parsing lève une erreur.
  • Pas de TTL sur les clés du cache : la mémoire grandit jusqu'à l'éviction ; passez toujours { ex }.
  • Faire confiance aveuglément à x-forwarded-for : prenez le premier hop, ou utilisez le helper IP de la plateforme, quand derrière un proxy.
  • Oublier pending sur les edge runtimes avec analytics ou limiters multi-région.

Quand NE PAS utiliser ce skill

  • Serveurs long-running avec une connexion TCP Redis déjà en place : gardez ioredis/node-redis.
  • Vector search ou RAG : utilisez un skill vector database à la place.
  • Caching in-process, sub-milliseconde : utilisez un LRU en mémoire.

Références

Skills similaires