ファランクスブログ

© 2026 all rights reserved.
  1. blog
  2. redis
  • Local:TCP
  • Upstash:HTTP
  • 使い方
  • option
  • RateLimit
  • zustandと合わせる

Redis:始める

2026年3月11日
  • Redisはメモリ(RAM)上にデータを保存・管理する専用のデータベース

    • Postgresはディスク(SSD/HDD)にデータを保存する

  • メモリにデータを保存するので読み書きが高速

Local:TCP

Upstashで触るサーバーレス用のHTTP Redisと書き方はほとんど変わらない。以下はDockerで触るためのセットアップ

docker-compose.yml:

services:
  redis:
    image: redis:8.2-alpine
    container_name: local-redis
    ports:
      - "6379:6379"
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis-data:/data
    restart: unless-stopped

volumes:
  redis-data:

redis.ts:

import Redis from "ioredis";

export const redis = new Redis({
  host: "127.0.0.1",
  port: 6379,
});

データのセット・取得:

const res = await redis.set("key2", "hello2", "EX", 10);

const value = await redis.get("key");
console.log(res , value); 
  • EXはTTL(有効期限)の設定

  • resには"OK"という文字のみが返る

Upstash:HTTP

Vercelから統合できる。環境変数名はVercelとUpstashで異なるが、値は同じ

KV_REST_API_TOKEN="*****************"
KV_REST_API_URL="***************"

UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=

使い方

redis.ts:

import { Redis } from "@upstash/redis";

export const redis = new Redis({
  url: process.env.KV_REST_API_URL,
  token: process.env.KV_REST_API_TOKEN,
});

api/redis/test/route.ts:

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

export async function GET() {
  const res = await redis.set("hello", "world", {});
  
  return Response.json(
    { message: "send redis" },
    {
      status: 200,
    },
  );
}

option

set(key , value , {})の {} に設定するオプション

await redis.set("hello", "world2", {
    ex: 30,
  });

RateLimit

route.ts:

import { redis } from "@/lib/redis";
import { NextRequest, NextResponse } from "next/server";

const MAX_REQUESTS = 5; // 60秒で5リクエスト
const WINDOW_SEC = 60;

export async function GET(req: NextRequest) {
  const ip = req.headers.get("x-forwarded-for")?.split(",")[0] || "unknown";
  const key = `ratelimit:${ip}`;

  const requests = await redis.incr(key);

  if (requests === 1) {
    await redis.expire(key, WINDOW_SEC);
  }

  if (requests > MAX_REQUESTS) {
    return NextResponse.json({ message: "Too Many Requests" }, { status: 429 });
  }

  // 成功時の処理
  await redis.set("hello", "world", {
    ex: 20,
  });
  const value = await redis.get("hello");

  return NextResponse.json({ message: "send redis", value });
}
  • redis.incrはkeyの値を1増やす

  • redis.expireはキーに有効期限を設定

    • request===1のとき、つまり初回リクエスト時に有効期限を設定しているので60秒にはキーが削除される

  • @upstash/ratelimit

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

zustandと合わせる

外部APIへの問い合わせを減らす

  • クライアント側はzustandを使ってリロードが行われるまでデータをキャッシュ、サーバー側はredisで設定した有効期限までは外部APIではなくredisに問い合わせる

  • 以下は天気APIを使った例

route.ts:

import { redis } from "@/lib/redis/redis";
import { NextRequest, NextResponse } from "next/server";

const API_KEY = process.env.OPENWEATHER_API_KEY!;

export async function GET(req: NextRequest) {
  const city = req.nextUrl.searchParams.get("city");
  if (!city) {
    return NextResponse.json({ error: "Missing city" }, { status: 400 });
  }

  const cacheKey = `city:${city.toLowerCase()}`;

  try {
    const cached = await redis.get(cacheKey);
    if (cached) {
      return NextResponse.json(cached);
    }

    const res = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`
    );

    if (!res.ok) {
      return NextResponse.json({ error: "Failed to fetch weather" }, { status: res.status });
    }

    const data = await res.json();

    await redis.set(cacheKey, JSON.stringify(data), { ex: 60 });

    return NextResponse.json(data);
  } catch (err) {
    console.error(err);
    return NextResponse.json({ error: "Internal server error" }, { status: 500 });
  }
}
  1. cacheKeyを宣言

  2. redisからcacheKeyキーを持つデータがあるか確認し、あればreturn

  3. cacheKeyが無い場合は外部APIからデータをfetchし、redisに保存した後にretun

weather-store.ts:

import { create } from "zustand";

export interface WeatherData {
  name: string;
  main: { temp: number; humidity: number };
  weather: { description: string; icon: string }[];
}

type WeatherStore = {
  weather: Record<string, WeatherData | null>;
  fetchWeather: (city: string) => Promise<WeatherData | null>;
};

export const useWeatherStore = create<WeatherStore>((set, get) => ({
  weather: {},
  fetchWeather: async (city: string) => {
    const cached = get().weather[city.toLowerCase()];
    if (cached) return cached;

    try {
      const res = await fetch(`/api/weather?city=${city}`, { cache: "no-store" });
      if (!res.ok) return null;

      const data: WeatherData = await res.json();
      set((state) => ({
        weather: { ...state.weather, [city.toLowerCase()]: data },
      }));
      return data;
    } catch (err) {
      console.error(err);
      return null;
    }
  },
}));

database
/
cache
redis
  • Local:TCP
  • Upstash:HTTP
  • 使い方
  • option
  • RateLimit
  • zustandと合わせる