取り組みの背景 — なぜエッジでキャッシュとレート制限が必要なのか
API を公開するとき、パフォーマンスとセキュリティは常にセットで考える必要があります。特にサーバーレス環境では、リクエストのたびにコールドスタートやデータベースへのアクセスが発生し、レスポンス速度に直結します。さらに、レート制限がなければ悪意あるリクエストがリソースを枯渇させるリスクもあります。
Upstash Redis は、HTTP ベースの REST API でアクセスできるサーバーレス Redis サービスです。TCP コネクションが使えない Cloudflare Workers のようなエッジ環境でも、そのまま利用できる点が大きな強みです。
Antigravity の AI エージェント支援を活用しながら、Upstash Redis によるエッジキャッシュとレート制限を Cloudflare Workers 上に実装する方法を順を追って整理していきます。
前提知識と環境準備
この記事を読み進めるにあたって、以下の基本的な知識があると理解がスムーズです。
- TypeScript の基本文法
- Cloudflare Workers の概要(Antigravity × Cloudflare Workers でAI搭載エッジアプリを構築する を先に読むのがおすすめです)
- REST API の基本
必要なツール
- Antigravity(最新版)
- Node.js 18 以上
- Upstash アカウント(upstash.com で無料プランから開始可能)
- Cloudflare アカウント(Workers の無料枠で十分)
Upstash Redis データベースの作成
まず Upstash Console にアクセスし、新しい Redis データベースを作成します。リージョンは利用者に近い場所を選択してください。作成後、以下の2つの値を控えておきます。
# Upstash Console の REST API セクションから取得
UPSTASH_REDIS_REST_URL=https://your-database.upstash.io
UPSTASH_REDIS_REST_TOKEN=AXxxxxxxxxxxxxxxxxxxxxxxxxxxxxプロジェクトのセットアップ
Antigravity のターミナルで、Hono フレームワークを使った Cloudflare Workers プロジェクトを作成します。Antigravity のエージェントに依頼すると、ボイラープレートを一瞬で生成してくれます。
# プロジェクト作成
npm create hono@latest my-edge-api -- --template cloudflare-workers
cd my-edge-api
# Upstash Redis クライアントをインストール
npm install @upstash/redis @upstash/ratelimit次に wrangler.toml に環境変数を設定します。
# wrangler.toml
name = "my-edge-api"
compatibility_date = "2026-03-01"
[vars]
ENVIRONMENT = "production"
# シークレットは wrangler secret put で設定
# wrangler secret put UPSTASH_REDIS_REST_URL
# wrangler secret put UPSTASH_REDIS_REST_TOKENエッジキャッシュの実装
Upstash Redis をキャッシュレイヤーとして使うことで、データベースへのアクセスを大幅に削減できます。以下は、API レスポンスを Redis にキャッシュするミドルウェアの実装例です。
// src/cache.ts
import { Redis } from "@upstash/redis/cloudflare";
import type { Context, Next } from "hono";
// Upstash Redis クライアントの初期化
function getRedis(c: Context): Redis {
return new Redis({
url: c.env.UPSTASH_REDIS_REST_URL,
token: c.env.UPSTASH_REDIS_REST_TOKEN,
});
}
// キャッシュミドルウェア
export function cacheMiddleware(ttlSeconds: number = 300) {
return async (c: Context, next: Next) => {
const redis = getRedis(c);
const cacheKey = `cache:${c.req.url}`;
// キャッシュヒットを試行
const cached = await redis.get<string>(cacheKey);
if (cached) {
// キャッシュヒット — Redis から即座にレスポンスを返す
return c.json(JSON.parse(cached), 200, {
"X-Cache": "HIT",
"X-Cache-TTL": String(ttlSeconds),
});
}
// キャッシュミス — オリジンへリクエストを通す
await next();
// 成功レスポンスをキャッシュに保存
if (c.res.status === 200) {
const body = await c.res.clone().text();
await redis.set(cacheKey, body, { ex: ttlSeconds });
c.res.headers.set("X-Cache", "MISS");
}
};
}
// 特定キーのキャッシュを手動で無効化
export async function invalidateCache(
redis: Redis,
pattern: string
): Promise<number> {
const keys = await redis.keys(`cache:${pattern}`);
if (keys.length === 0) return 0;
// パイプラインで一括削除(ネットワークラウンドトリップを最小化)
const pipeline = redis.pipeline();
keys.forEach((key) => pipeline.del(key));
await pipeline.exec();
return keys.length;
}X-Cache ヘッダーを付与しているので、レスポンスがキャッシュから返されたのかオリジンから返されたのかを簡単に判別できます。
レート制限の実装
Upstash の @upstash/ratelimit パッケージを使えば、スライディングウィンドウ方式のレート制限をわずか数行で実装できます。
// src/ratelimit.ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis/cloudflare";
import type { Context, Next } from "hono";
// レート制限ミドルウェア
export function rateLimitMiddleware(
maxRequests: number = 60,
windowSeconds: number = 60
) {
return async (c: Context, next: Next) => {
const redis = new Redis({
url: c.env.UPSTASH_REDIS_REST_URL,
token: c.env.UPSTASH_REDIS_REST_TOKEN,
});
const ratelimit = new Ratelimit({
redis,
// スライディングウィンドウ: windowSeconds 秒間に maxRequests 回まで
limiter: Ratelimit.slidingWindow(maxRequests, `${windowSeconds} s`),
analytics: true, // Upstash Console でアナリティクスを確認可能
});
// クライアントの識別子(IP アドレスまたは API キー)
const identifier =
c.req.header("X-API-Key") ||
c.req.header("CF-Connecting-IP") ||
"anonymous";
const { success, limit, remaining, reset } =
await ratelimit.limit(identifier);
// レート制限ヘッダーを常に付与
c.res.headers.set("X-RateLimit-Limit", String(limit));
c.res.headers.set("X-RateLimit-Remaining", String(remaining));
c.res.headers.set("X-RateLimit-Reset", String(reset));
if (!success) {
// レート制限超過 — 429 Too Many Requests を返す
return c.json(
{
error: "Too Many Requests",
message: `Rate limit exceeded. Try again after ${new Date(reset).toISOString()}`,
retryAfter: Math.ceil((reset - Date.now()) / 1000),
},
429
);
}
await next();
};
}スライディングウィンドウ方式は、固定ウィンドウ方式と比べて境界付近でのバースト(突発的な大量リクエスト)を防げるため、より公平なレート制限が実現できます。
メインアプリケーションへの統合
作成したミドルウェアを Hono アプリケーションに組み込みます。
// src/index.ts
import { Hono } from "hono";
import { cors } from "hono/cors";
import { cacheMiddleware, invalidateCache } from "./cache";
import { rateLimitMiddleware } from "./ratelimit";
import { Redis } from "@upstash/redis/cloudflare";
type Bindings = {
UPSTASH_REDIS_REST_URL: string;
UPSTASH_REDIS_REST_TOKEN: string;
};
const app = new Hono<{ Bindings: Bindings }>();
// グローバルミドルウェア
app.use("*", cors());
app.use("/api/*", rateLimitMiddleware(100, 60)); // 60秒間に100リクエストまで
// キャッシュ付きエンドポイント(5分間キャッシュ)
app.get("/api/articles", cacheMiddleware(300), async (c) => {
// 実際のデータ取得処理(DB や外部 API)
const articles = await fetchArticlesFromDB();
return c.json({ articles, timestamp: new Date().toISOString() });
});
// キャッシュなしエンドポイント(リアルタイム性が必要なデータ)
app.get("/api/stats", async (c) => {
const redis = new Redis({
url: c.env.UPSTASH_REDIS_REST_URL,
token: c.env.UPSTASH_REDIS_REST_TOKEN,
});
// Redis をリアルタイムカウンターとして使用
const pageViews = await redis.incr("stats:pageviews");
return c.json({ pageViews });
});
// キャッシュ無効化エンドポイント(管理者用)
app.post("/api/cache/purge", async (c) => {
const redis = new Redis({
url: c.env.UPSTASH_REDIS_REST_URL,
token: c.env.UPSTASH_REDIS_REST_TOKEN,
});
const deleted = await invalidateCache(redis, "*articles*");
return c.json({ purged: deleted });
});
// ダミーのデータ取得関数(実際のプロジェクトではDBアクセスに置き換え)
async function fetchArticlesFromDB() {
return [
{ id: 1, title: "Getting Started with Edge Computing" },
{ id: 2, title: "Redis Patterns for Serverless" },
];
}
export default app;Antigravity のエージェントを活用した開発のコツ
Antigravity のエージェントモードを使えば、上記のようなインフラコードの生成・リファクタリングが格段にスムーズになります。
実際の開発で役立つプロンプト例をいくつか紹介します。
# キャッシュ戦略の相談
「このAPIエンドポイントに最適なキャッシュTTLを提案して。
- /api/articles → 更新頻度: 1日数回
- /api/user/profile → 更新頻度: ユーザー操作時
- /api/stats → リアルタイム性が必要」
# レート制限の設計
「以下の要件でレート制限を設計して:
- 無料ユーザー: 60 req/min
- Proユーザー: 300 req/min
- APIキー認証ベースで識別」
Antigravity のエージェントは wrangler.toml の設定や TypeScript の型定義も一括で生成してくれるので、設定漏れを防ぎやすいのも利点です(Antigravity × Hono でサーバーレスAPIを構築する も参考にしてください)。
デプロイとテスト
Wrangler を使ってデプロイし、動作を確認しましょう。
# ローカル開発
npx wrangler dev
# シークレットの設定
npx wrangler secret put UPSTASH_REDIS_REST_URL
npx wrangler secret put UPSTASH_REDIS_REST_TOKEN
# 本番デプロイ
npx wrangler deployデプロイ後、以下のコマンドでキャッシュとレート制限の動作を確認します。
# 1回目: キャッシュ MISS
curl -i https://my-edge-api.your-subdomain.workers.dev/api/articles
# → X-Cache: MISS
# 2回目: キャッシュ HIT(高速レスポンス)
curl -i https://my-edge-api.your-subdomain.workers.dev/api/articles
# → X-Cache: HIT
# レート制限テスト(100回連続リクエスト)
for i in $(seq 1 105); do
echo "Request $i: $(curl -s -o /dev/null -w '%{http_code}' https://my-edge-api.your-subdomain.workers.dev/api/articles)"
done
# → 101回目以降で 429 が返るよくあるエラーと対処法
Upstash 接続エラー
UPSTASH_REDIS_REST_URL または UPSTASH_REDIS_REST_TOKEN が正しく設定されていない場合、以下のようなエラーが出ます。
Error: Unauthorized - Invalid token
wrangler secret list でシークレットが登録されているか確認してください。ローカル開発では .dev.vars ファイルに環境変数を記述します。
キャッシュが更新されない
TTL の設定が長すぎると、データ更新後も古いキャッシュが返され続けます。コンテンツ更新時には /api/cache/purge エンドポイントを叩くか、Write-Through キャッシュパターンを採用してください。
レート制限が厳しすぎる/緩すぎる
Upstash Console のアナリティクスダッシュボードで実際のリクエスト分布を確認し、maxRequests と windowSeconds の値を調整しましょう。analytics: true を設定しておくと、Upstash Console から視覚的にトラフィックパターンを分析できます。
まとめ
Antigravity と Upstash Redis を組み合わせることで、Cloudflare Workers 上にエッジキャッシュとレート制限を備えた高速・安全な API を構築できます。
本記事で扱ったポイントを整理すると、以下のとおりです。
- Upstash Redis は HTTP REST API でアクセスできるため、TCP が使えないエッジ環境でも利用可能
- キャッシュミドルウェア でデータベースの負荷を大幅に削減し、レスポンス速度を向上
- レート制限ミドルウェア でスライディングウィンドウ方式の公平なアクセス制御を実現
- Antigravity のエージェント がインフラコードの生成・設定を支援し、開発効率を向上
次のステップとして、Antigravity でセキュアな API バックエンドを設計する を読んで、認証・認可の実装も合わせて進めてみてください。