api-database-vercel-kv
Serverless Redis-compatible key-value store via Upstash REST API -- edge-compatible, automatic JSON serialization, TTL-based caching
Works with
---
name: api-database-vercel-kv
description: Serverless Redis-compatible key-value store via Upstash REST API -- edge-compatible, automatic JSON serialization, TTL-based caching
license: MIT
---
# Vercel KV / Upstash Redis Patterns
> **Quick Guide:** Use `@upstash/redis` (the successor to `@vercel/kv`) for serverless, edge-compatible Redis via REST API. Key gotchas: REST adds ~5-15ms latency per call vs TCP Redis, all values are auto-serialized as JSON (objects round-trip transparently but `Date` objects become strings), pipeline/multi execute as single HTTP requests but pipeline is NOT atomic. Use `Redis.fromEnv()` for automatic connection. Always set TTLs -- serverless Redis is billed per command.
---
<critical_requirements>
## CRITICAL: Before Using This Skill
> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
**(You MUST use `@upstash/redis` for new projects -- `@vercel/kv` was deprecated in December 2024 and all stores were migrated to Upstash Redis)**
**(You MUST set TTLs on all cached data -- serverless Redis is billed per command and has storage limits per plan)**
**(You MUST understand that this is a REST/HTTP client, NOT a TCP Redis client -- each command is an HTTP request with ~5-15ms overhead, so batch with pipelines when possible)**
</critical_requirements>
---
## Examples
- [Core Patterns](examples/core.md) -- Client setup, CRUD operations, TTL, hashes, pipelines, transactions, rate limiting, sessions
**Additional resources:**
- [reference.md](reference.md) -- Command quick reference, environment variables, plan limits
---
**Auto-detection:** Vercel KV, @vercel/kv, @upstash/redis, Upstash Redis, KV_REST_API_URL, KV_REST_API_TOKEN, UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN, Redis.fromEnv, kv.set, kv.get, kv.hset, kv.hget, kv.incr, kv.expire, kv.del, createClient, automaticDeserialization, edge Redis, serverless Redis
**When to use:**
- Caching API responses or database queries in Vercel serverless/edge functions
- Rate limiting at the edge (sliding window counters)
- Session storage for serverless applications
- Feature flags, A/B test assignments, or short-lived counters
- Any Redis use case on Vercel where TCP connections are unavailable (edge runtime)
**Key patterns covered:**
- Client initialization (`Redis.fromEnv()`, `new Redis()`)
- Basic CRUD with automatic JSON serialization
- TTL and expiration strategies
- Hash operations for structured data
- Pipelines (batched HTTP) and transactions (atomic MULTI/EXEC)
- Rate limiting with sorted sets
- Session storage patterns
**When NOT to use:**
- High-throughput, low-latency Redis workloads (use ioredis with TCP -- REST adds per-request overhead)
- Pub/Sub subscribers (REST is request-response, not persistent connections)
- Redis Streams consumers (requires TCP client like ioredis)
- Large value storage (>1 MB per record on free tier, billed by command count)
- Primary database (Redis is a cache/ephemeral store, not a source of truth)
---
<philosophy>
## Philosophy
Upstash Redis (formerly Vercel KV) is a **serverless, REST-based Redis** designed for edge and serverless runtimes where TCP connections are unavailable or impractical. The core trade-off: **HTTP compatibility everywhere, at the cost of per-request latency overhead.**
**Core principles:**
1. **REST-first** -- Every Redis command is an HTTP request. This works everywhere (edge, serverless, browsers) but adds ~5-15ms per call. Batch with pipelines.
2. **Auto-serialization** -- Objects are JSON-serialized on write and deserialized on read. This is convenient but means `Date` objects, `Map`, `Set`, and functions are not preserved faithfully.
3. **Ephemeral by design** -- Set TTLs on everything. Serverless Redis is billed per command and has storage caps. Treat it as a cache, not a database.
4. **Zero connection management** -- No connection pools, no reconnection logic, no `error` event handlers. Each request is stateless HTTP.
</philosophy>
---
<patterns>
## Core Patterns
> Full implementations with good/bad pairs: [examples/core.md](examples/core.md)
### Pattern 1: Client Initialization
Two approaches: `Redis.fromEnv()` (preferred on Vercel -- reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` automatically) or `new Redis({ url, token })` for explicit configuration. Never hardcode credentials.
```typescript
import { Redis } from "@upstash/redis";
const redis = Redis.fromEnv();
export { redis };
```
---
### Pattern 2: Automatic JSON Serialization
The SDK auto-serializes objects to JSON on write and deserializes on read. Never call `JSON.stringify` manually -- it causes double-serialization. Use `get<T>()` for typed returns, `satisfies` for type-safe writes. `Date` objects become ISO strings on round-trip -- store timestamps as numbers instead.
```typescript
await redis.set("user:123", data satisfies UserProfile, { ex: TTL_SECONDS });
const user = await redis.get<UserProfile>("user:123"); // UserProfile | null
```
---
### Pattern 3: TTL and Expiration
Always set TTLs -- serverless Redis is billed per command. Use `{ ex: seconds }` or `{ px: milliseconds }` on `set()`. Use `{ nx: true }` for distributed locks (returns `"OK"` or `null`). Keys without TTLs cause unbounded storage growth.
```typescript
await redis.set("cache:key", data, { ex: CACHE_TTL_SECONDS });
```
---
### Pattern 4: Hash Operations
Hashes enable partial field reads/writes without serializing entire objects. Use `hset` for multi-field writes, `hget`/`hgetall` for reads, `hincrby` for atomic counters. Note: `hset` does not accept TTL directly -- call `expire()` separately. `hgetall` returns `null` for missing keys (not `{}`).
---
### Pattern 5: Pipelines and Transactions
**Pipelines** (`redis.pipeline()`) batch commands into a single HTTP request but are NOT atomic. **Transactions** (`redis.multi()`) provide atomic MULTI/EXEC, also as a single HTTP request. Avoid sequential calls when multiple commands can be batched -- each call is a separate HTTP round-trip.
```typescript
const pipe = redis.pipeline();
pipe.set("k1", "v1", { ex: TTL });
pipe.incr("counter");
const results = await pipe.exec<[string, number]>();
```
**Important:** Upstash REST transactions do NOT support `WATCH` for optimistic locking.
---
### Pattern 6: Rate Limiting (Sliding Window)
Sliding window via sorted set scores -- `zadd` with timestamp as score, `zremrangebyscore` to prune expired entries, `zcard` to count, all batched in a pipeline. For production rate limiting, consider `@upstash/ratelimit` which provides built-in algorithms.
---
### Pattern 7: Cache-Aside Helper
Generic `cacheAside<T>(key, fetcher, ttl)` pattern: check cache first, fetch on miss, fire-and-forget cache write to avoid blocking responses on cache failures.
</patterns>
---
<decision_framework>
## Decision Framework
### Upstash Redis vs ioredis/node-redis?
```
Which Redis client should I use?
+-- Running in Vercel Edge Runtime? -> @upstash/redis (only option -- no TCP)
+-- Running in Vercel Serverless Functions? -> @upstash/redis (simpler) or ioredis (if you need TCP features)
+-- Need Pub/Sub subscribers? -> ioredis (REST cannot maintain subscriptions)
+-- Need Redis Streams consumers? -> ioredis (requires persistent TCP connection)
+-- Need lowest possible latency (<1ms)? -> ioredis with TCP (REST adds HTTP overhead)
+-- Simple caching/sessions/counters? -> @upstash/redis (zero connection management)
```
### Pipeline vs Transaction vs Sequential?
```
How should I batch commands?
+-- Need atomicity (all-or-nothing)? -> redis.multi() (transaction)
+-- Just reducing HTTP round-trips? -> redis.pipeline() (non-atomic batch)
+-- Single independent command? -> Direct call (redis.set, redis.get, etc.)
```
</decision_framework>
---
<red_flags>
## RED FLAGS
**High Priority Issues:**
- Using `@vercel/kv` in new projects -- deprecated December 2024, use `@upstash/redis` instead
- Missing TTLs on cached keys -- causes unbounded storage growth and unexpected billing
- Manual `JSON.stringify`/`JSON.parse` with Upstash Redis -- causes double-serialization because the SDK auto-serializes all values
- Assuming pipeline commands are atomic -- pipelines batch for HTTP efficiency but do NOT guarantee atomicity (use `multi()` for atomic execution)
**Medium Priority Issues:**
- Making sequential Redis calls where a pipeline would work -- each call is a separate HTTP round-trip (~5-15ms each)
- Storing values >1 MB -- REST requests have size limits per plan (100 MB max on free/pay-as-you-go, but large values degrade performance)
- Using Upstash Redis as a primary database -- it's a cache/ephemeral store, always have a source of truth elsewhere
**Common Mistakes:**
- Expecting `hgetall` to return an empty object `{}` for missing keys -- Upstash returns `null` (unlike ioredis which returns `{}`)
- Forgetting that `get()` returns `null` (not `undefined`) for missing keys
- Passing `Date` objects and expecting them to survive round-trip -- they serialize to ISO strings and come back as strings, not `Date` instances
**Gotchas & Edge Cases:**
- `automaticDeserialization: false` breaks many TypeScript types -- only disable if you need raw string responses and are prepared to handle typing manually
- `set` with `ex` option resets TTL on overwrite (standard Redis behavior) -- if you `set` a key that already has a TTL, the new `ex` value replaces it
- REST latency is per-request, not per-command -- a pipeline with 10 commands has the same HTTP overhead as a single command (one round-trip)
- Free tier is limited to 500K commands/month and 256 MB storage -- monitor usage in production
- `nx` (set-if-not-exists) returns `null` on failure, `"OK"` on success -- check the return value explicitly
</red_flags>
---
<critical_reminders>
## CRITICAL REMINDERS
> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
**(You MUST use `@upstash/redis` for new projects -- `@vercel/kv` was deprecated in December 2024 and all stores were migrated to Upstash Redis)**
**(You MUST set TTLs on all cached data -- serverless Redis is billed per command and has storage limits per plan)**
**(You MUST understand that this is a REST/HTTP client, NOT a TCP Redis client -- each command is an HTTP request with ~5-15ms overhead, so batch with pipelines when possible)**
**Failure to follow these rules will cause deprecated package usage, unbounded storage costs, and unnecessary latency in serverless functions.**
</critical_reminders>More API Design skills
lark-event
larksuite/cli
Lark/Feishu real-time event listening / subscribing / consuming: stream events as NDJSON via `lark-cli event consume <EventKey>` (covers IM messages/reactions/chat changes, Approval status changes, Task updates, VC meeting started/joined/ended, Minutes generated, Whiteboard updated, etc.). Use for Lark bots, real-time message processing, long-running subscribers, streaming webhook/push handlers. Supports `--max-events` / `--timeout` bounded runs and a stderr ready-marker contract — designed for AI agents running as subprocesses.
lark-contact
larksuite/cli
飞书 / Lark 通讯录:按姓名 / 邮箱解析成 open_id,或按 open_id 反查姓名 / 部门 / 邮箱 / 联系方式 / 个人状态 / 签名,以及按关键词搜索当前用户可见的机器人 / 智能体(agent)。当用户提到一个名字要下一步发消息 / 排日程,或拿到 open_id 想查具体信息时使用。不负责部门树遍历、按部门列员工、组织架构图,这类需求走原生 OpenAPI。
lark-openapi-explorer
larksuite/cli
飞书/Lark 原生 OpenAPI 探索:从官方文档库中挖掘未经 CLI 封装的原生 OpenAPI 接口。当用户的需求无法被现有 lark-* skill 或 lark-cli 已注册命令满足,需要查找并调用原生飞书 OpenAPI 时使用。

