upstash-redis — GitHub Copilot skill: what it does, how to install
Connect to Redis over HTTP in serverless runtimes using @upstash/redis and configure rate limiting with @upstash/ratelimit without TCP connection pooling.
npx skills add github/awesome-copilot --skill upstash-redisWhat upstash-redis does
This skill guides Copilot through implementing Redis operations over HTTP for Next.js route handlers, Cloudflare Workers, and Vercel. It provides step-by-step patterns for cache-aside with TTL, session storage with sliding expiration, and 429 rate limiters using sliding window or token bucket algorithms.
Every command runs as an HTTP request via global fetch. The SDK automatically serializes objects and arrays, so manual calls to JSON.stringify or parseInt will break data handling. Setup requires an Upstash Redis database along with UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN environment variables.
Install this if you deploy on edge runtimes or serverless platforms where TCP sockets are unavailable. Keep standard clients like ioredis on long-running servers, and avoid this skill for vector similarity search.
What it helps with
- implement cache-aside caching with TTLs in serverless handlers
- set up a 429 rate limiter using sliding window or token bucket
- manage user sessions with sliding expiration times
- query Redis over HTTP without managing TCP connection pools
Example prompts for the agent
- Implement a sliding window rate limiter for this Next.js route handler using Upstash
- Set up cache-aside with a 1-hour TTL for the getUser query using @upstash/redis
- Create a session store in Upstash Redis with sliding 24-hour expiration
How to install upstash-redis
1Skills CLI (Vercel)
npx skills add github/awesome-copilot --skill upstash-redis2GitHub CLI
gh skill install github/awesome-copilot upstash-redis3Manually: put the files in place
.github/skills/upstash-redis/SKILL.md1Skills CLI (Vercel)
npx skills add github/awesome-copilot --skill upstash-redis -a claude-code2Manually: put the files in place
~/.claude/skills/upstash-redis/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/upstash-redis/
Manually: put the files in place
.agents/skills/upstash-redis/SKILL.mdFor all projects use ~/.agents/skills/
Manually: put the files in place
.cursor/skills/upstash-redis/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
Platform CLI
gemini skills install https://github.com/github/awesome-copilot --path skills/upstash-redisManually: put the files in place
.devin/skills/upstash-redis/SKILL.mdThe legacy .windsurf/skills/ path still works
Claude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
From the same set554
The whole set →Similar
Questions about upstash-redis
How to install upstash-redis in GitHub Copilot?
Run npx skills add github/awesome-copilot --skill upstash-redis in your project or add .github/skills/upstash-redis/SKILL.md.
What does the upstash-redis skill do?
It gives Copilot instructions to configure @upstash/redis and @upstash/ratelimit over HTTP in serverless and edge runtimes.
When should you avoid this skill?
Skip it on long-running servers with existing TCP connections like ioredis, or when you need vector similarity search.
Source text
SKILL.md · MIT
---
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
© Upstash · MIT · github/awesome-copilot