upstash-redis

$npx mdskill add github/awesome-copilot/upstash-redis

This skill covers the three things serverless apps most often need Redis for: caching, sessions, and rate limiting. The client talks to Redis over HTTP, so it works where a long-lived TCP connection does not (edge middleware, short lived functions). Follow the steps in order; each ends with a checkpoint.

SKILL.md

.github/skills/upstash-redisView on GitHub ↗
---
name: upstash-redis
description: >
  Use Redis over HTTP from serverless and edge runtimes with @upstash/redis, and
  add rate limiting with @upstash/ratelimit. Use when the user mentions Upstash
  Redis, needs Redis from a Next.js route handler or middleware, Vercel,
  Cloudflare Workers, Deno, or Bun without TCP connection pooling, or wants
  cache-aside with TTLs, a session store, counters, or a 429 rate limiter using
  fixed window, sliding window, or token bucket. DO NOT use for self-hosted or
  TCP Redis clients (ioredis, node-redis), Redis Cluster administration, or
  vector similarity search.
license: MIT
compatibility: "@upstash/redis 1.x, @upstash/ratelimit 2.x, Node.js 18+ or any runtime with global fetch"
metadata:
  author: Upstash
---

# Upstash Redis Skill

This skill covers the three things serverless apps most often need Redis for:
caching, sessions, and rate limiting. The client talks to Redis over HTTP, so
it works where a long-lived TCP connection does not (edge middleware, short
lived functions). Follow the steps in order; each ends with a checkpoint.

## Requirements and limitations

- An Upstash Redis database (hosted service; usage-based pricing with a free
  tier). Credentials are a REST URL and token from the database page.
- Environment variables `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.
- Every command is an HTTP request. Batch with `pipeline()` or `MGET`/`MSET`
  when you issue many commands per request; avoid `KEYS *` in production.
- Values are serialized automatically (objects, arrays, numbers round-trip).
  Do not `JSON.stringify` before `set` or `parseInt` after `get`.

## Step 1 — Install and create one client per module

```bash
npm install @upstash/redis @upstash/ratelimit
```

```ts
// lib/redis.ts
import { Redis } from "@upstash/redis";

// Reads UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN
export const redis = Redis.fromEnv();
```

Create the client at module scope, not inside the request handler, so
ephemeral caches and pipelines can be reused across invocations.

> **Checkpoint**: `await redis.ping()` returns `"PONG"`.

## Step 2 — Cache-aside with TTL

```ts
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); // your data source
  if (user) await redis.set(key, user, { ex: 3600 }); // 1 hour TTL
  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}`); // invalidate
}
```

Always set a TTL on cache entries; namespace keys (`user:123`, `session:abc`).

> **Checkpoint**: second call to `getUser` returns without hitting the database
> and `await redis.ttl("user:123")` is positive.

## Step 3 — Sessions with sliding expiration

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

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

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); // slide
  return session;
}

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

Store the session id in an `HttpOnly; Secure; SameSite` cookie; never put the
Redis token in client code.

> **Checkpoint**: `getSession` after `createSession` returns the object with
> `userId`; after `destroySession` it returns `null`.

## Step 4 — Rate limiting a route handler

```ts
// app/api/search/route.ts (Next.js App Router; same pattern for any 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 requests per 10 seconds
  prefix: "ratelimit:search", // isolate keys per 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))),
      },
    });
  }

  // handle the request
  return Response.json({ ok: true });
}
```

- Identifier: use the user id or API key when authenticated; fall back to IP.
- Algorithms: `Ratelimit.fixedWindow(n, "1 m")` (cheapest), `slidingWindow`
  (smooth boundaries, default choice), `tokenBucket(refill, "10 s", max)`
  (allows bursts). Windows accept `ms`, `s`, `m`, `h`, `d`.
- Tiers: create one `Ratelimit` per tier with different `prefix` values.
- Edge middleware / Cloudflare Workers with `analytics: true`: the result has a
  `pending` promise; pass it to `context.waitUntil(pending)` so background work
  finishes before the runtime exits.
- `reset` is a Unix timestamp in milliseconds.

> **Checkpoint**: the 11th request within 10 seconds returns 429 with a
> `Retry-After` header; after the window it succeeds again.

## Common pitfalls

- **Creating clients inside handlers**: the limiter's in-memory
  `ephemeralCache` only helps when the instance outlives the request.
- **Manual JSON**: `redis.set("k", JSON.stringify(v))` then `redis.get` returns
  an already-parsed object; double parsing throws.
- **No TTL on cache keys**: memory grows until eviction; always pass `{ ex }`.
- **Trusting `x-forwarded-for` blindly**: take the first hop, or use the
  platform's IP helper, when behind a proxy.
- **Forgetting `pending`** on edge runtimes with analytics or multi-region
  limiters.

## When NOT to use this skill

- Long-running servers with a TCP Redis connection already in place: keep
  ioredis/node-redis.
- Vector search or RAG: use a vector database skill instead.
- Sub-millisecond, in-process caching: use an in-memory LRU.

## References

- https://upstash.com/docs/redis/sdks/ts/overview
- https://upstash.com/docs/redis/sdks/ratelimit-ts/overview
- https://github.com/upstash/redis-js
- https://github.com/upstash/ratelimit-js

More from github/awesome-copilot

SkillDescription
acquire-codebase-knowledgeUse this skill when the user explicitly asks to map, document, or onboard into an existing codebase. Trigger for prompts like "map this codebase", "document this architecture", "onboard me to this repo", or "create codebase docs". Do not trigger for routine feature implementation, bug fixes, or narrow code edits unless the user asks for repository-level discovery.
acreadiness-assessRun the AgentRC readiness assessment on the current repository and produce a static HTML dashboard at reports/index.html. Wraps `npx github:microsoft/agentrc readiness` and hands off rendering to the @ai-readiness-reporter custom agent. Supports policies (--policy) for org-specific scoring. Use when asked to assess, audit, or score the AI readiness of a repo.
acreadiness-generate-instructionsGenerate tailored AI agent instruction files via AgentRC instructions command. Produces .github/copilot-instructions.md (default, recommended for Copilot in VS Code) plus optional per-area .instructions.md files with applyTo globs for monorepos. Use after running /acreadiness-assess to close gaps in the AI Tooling pillar.
acreadiness-policyHelp the user pick, write, or apply an AgentRC policy. Policies customise readiness scoring by disabling irrelevant checks, overriding impact/level, setting pass-rate thresholds, or chaining org baselines with team overrides. Use when the user asks about strict mode, AI-only scoring, custom weights, CI gating, or wants org-wide standardisation.
ad-campaign-analyzerUse this skill when the user shares ad campaign performance data and asks what to cut, scale, or test. Trigger for prompts like "analyze my ad campaigns", "where am I wasting ad spend", "reallocate my ad budget", "which ads are actually working", or "ROAS analysis". Do not trigger for campaign planning or creative generation without performance data.
add-educational-comments'Add educational comments to the file specified, or prompt asking for file to comment if one is not provided.'
adobe-illustrator-scriptingWrite, debug, and optimize Adobe Illustrator automation scripts using ExtendScript (JavaScript/JSX). Use when creating or modifying scripts that manipulate documents, layers, paths, text frames, colors, symbols, artboards, or any Illustrator DOM objects. Covers the complete JavaScript object model, coordinate system, measurement units, export workflows, and scripting best practices.
agent-architectureDesign AI agent architectures through requirements discovery, or audit and diagnose architectural flaws in existing agents. Architecture only; excludes implementation and general code review.
agent-governance|
agent-owasp-compliance|