RemixでUpstash Redisをセッションストアとして使用する方法
フルスタックWebフレームワークである Remix は、一般的なWebサーバーのユースケースに対応するためのAPIを提供しています。この記事では、セッション管理に焦点を当て、なぜそしてどのように Upstash Redis をセッションストアとして活用できるかを解説します。
セッションとは?
Remix公式ドキュメントにセッションの優れた入門ガイドがあります:https://remix.run/docs/en/v1/api/remix#sessions
簡単に言うと、セッションはサーバーとクライアント間でユーザーデータや状態を共有するための仕組みです。主な用途には、ユーザー認証状態の追跡、ショッピングカートの状態管理、フラッシュメッセージの表示などがあります。
なぜUpstash Redisを使うのか?
セッションデータはサーバー側に保存されます。しかし、サーバーレスインフラやPaaS(例:Heroku)にデプロイする場合、サーバーのファイルシステムにデータを永続化することはできません。サーバーレス環境ではリクエストごとにファイルシステムが変わる可能性があり、PaaSではデプロイごとにリセットされるためです。データを永続化するには、外部データベースにユーザーデータを保存する必要があります。Upstash Redisは、以下の理由からセッションデータの保存に最適なソリューションです:
- キー・バリュー構造との親和性:セッションは本質的に
key:valueのデータ構造(キー=セッションID、値=シリアライズされたデータ)を持ち、Redisのデータモデルと完全に一致します。 - 組み込みの期限切れ機能:RedisにはTTL(Time To Live)機能があり、期限切れセッションのクリーンアップ作業が不要になります。
- 暗号化ストレージ:セッションには機密ユーザーデータが含まれる場合がありますが、Upstash Redisは保存データをすべて暗号化します。
- シンプルなHTTP REST API:UpstashはHTTPベースのREST APIを提供しており、サーバーレス環境からの通信に最も適しています。
UpstashをRemixのセッションプロバイダーとして使用する手順
この記事は、筆者が作成した Redis Session Storage Using Upstash Example に基づいています。Remixリポジトリをクローンして実際に試してみてください。
ステップ1:Upstash APIキーを取得する
- Upstash にアクセスし、新規アカウントを作成する
- 新しいRedisデータベースを作成する
UPSTASH_REDIS_REST_URLとUPSTASH_REDIS_REST_TOKENをコピーし、Remixプロジェクトのルートにある.envファイルに保存するdotenvパッケージをインストールする:npm install --save-dev dotenv(作成した.envファイルから環境変数を読み込むため)package.jsonのdevスクリプトをremix devからdotenv/config node_modules/.bin/remix devに変更する
ステップ2:createSessionStorage を実装してUpstashセッション統合を作成する
Remixは createSessionStorage ファクトリ関数を提供しており、独自のセッション統合を簡単に構築できます。これを使ってUpstash連携を実装します。
// sessions/upstash.server.ts
import * as crypto from "crypto";
import { createSessionStorage } from "@remix-run/node"; // または "remix" (v1の場合)
const upstashRedisRestUrl = process.env.UPSTASH_REDIS_REST_URL;
const headers = {
Authorization: `Bearer ${process.env.UPSTASH_REDIS_REST_TOKEN}`,
Accept: "application/json",
"Content-Type": "application/json",
};
const expiresToSeconds = (expires: Date) => {
const now = new Date();
const expiresDate = new Date(expires);
const secondsDelta = Math.floor((expiresDate.getTime() - now.getTime()) / 1000);
return secondsDelta < 0 ? 0 : secondsDelta;
};
// 詳細は https://remix.run/docs/en/main/utils/sessions#createsessionstorage を参照
export function createUpstashSessionStorage({ cookie }: { cookie: any }) {
return createSessionStorage({
cookie,
async createData(data, expires) {
// ランダムなIDを生成(Remixコアの createFileSessionStorage と同じ方式)
const randomBytes = crypto.randomBytes(8);
const id = Buffer.from(randomBytes).toString("hex");
// Upstash Redis HTTP APIを呼び出し、Cookieの有効期限に基づいてTTLを設定
await fetch(
`${upstashRedisRestUrl}/set/${id}?EX=${expiresToSeconds(expires)}`,
{
method: "post",
body: JSON.stringify({ data }),
headers,
}
);
return id;
},
async readData(id) {
const response = await fetch(`${upstashRedisRestUrl}/get/${id}`, { headers });
try {
const { result } = await response.json();
return result ? JSON.parse(result).data : null;
} catch (error) {
return null;
}
},
async updateData(id, data, expires) {
await fetch(
`${upstashRedisRestUrl}/set/${id}?EX=${expiresToSeconds(expires)}`,
{
method: "post",
body: JSON.stringify({ data }),
headers,
}
);
},
async deleteData(id) {
await fetch(`${upstashRedisRestUrl}/del/${id}`, {
method: "post",
headers,
});
},
});
}
実装のポイント
- 渡される
cookieオブジェクトにはexpiresプロパティ(Date型)として有効期限が含まれます。expiresToSeconds関数でこれを秒数に変換し、RedisのEXパラメータとして渡しています。 - Cookieに有効期限を設定することを忘れないでください。Redis側で自動的に期限切れセッションが削除されます。
- セッションIDの生成には
crypto.randomBytesを使用しています。これはRemixコアのcreateFileSessionStorageと同じ方式です。他のUUIDライブラリなどを使っても構いません。
ステップ3:アプリケーションでセッションストレージを使用する
独自のセッションストレージ実装が完成したので、実際にアプリで使えるように設定します。
ここから先はUpstash固有のロジックは一切登場しません。すべて
sessions/upstash.server.tsにカプセル化されています。
// sessions.server.ts
import { createCookie } from "@remix-run/node"; // または "remix" (v1の場合)
import { createUpstashSessionStorage } from "~/sessions/upstash.server";
// セッションの有効期間を設定(例:10秒)。本番では適切な値に変更してください。
const EXPIRATION_DURATION_IN_SECONDS = 60 * 60 * 24; // 例:1日
const expires = new Date();
expires.setSeconds(expires.getSeconds() + EXPIRATION_DURATION_IN_SECONDS);
const sessionCookie = createCookie("__session", {
secrets: [process.env.SESSION_SECRET || "r3m1xr0ck1"], // 環境変数から取得推奨
sameSite: "lax", // true は非推奨、"lax" または "strict" を指定
expires,
httpOnly: true,
secure: process.env.NODE_ENV === "production",
path: "/",
});
const { getSession, commitSession, destroySession } =
createUpstashSessionStorage({ cookie: sessionCookie });
export { getSession, commitSession, destroySession };
sessions.server.ts ファイルを作成し、上記コードを貼り付けます。このファイルは getSession、commitSession、destroySession の3つの関数をエクスポートし、アプリケーションがセッションとやり取りするためのインターフェースを提供します。また、クライアント側にセッション参照を保存するためのCookieもここで定義しています。
有効期限はビジネス要件に合わせて設定してください。詳細は MDN Web Docs - Cookies を参照してください。
Remixルートでのセッション利用
Remixではルートごとにセッションの利用を定義できます。以下は routes/index.tsx でセッションを扱う例です。セッションAPIの使い方を示すためのもので、具体的なビジネスロジックとの接続はスコープ外としています。
認証にセッションを使う例をお探しの場合は、remix-auth-form の例 を参照してください。
// routes/index.tsx
import type { LoaderFunction } from "@remix-run/node";
import { json, useLoaderData } from "@remix-run/react";
import { commitSession, getSession } from "~/sessions.server";
export const loader: LoaderFunction = async ({ request }) => {
// Cookieからセッションを取得
const session = await getSession(request.headers.get("Cookie"));
const myStoredData = session.get("myStoredData");
// セッションが見つからない場合(未作成または期限切れ)→ 新規作成
if (!myStoredData) {
session.set("myStoredData", "Some data");
return json(
{ message: "Created new session" },
{
headers: {
"Set-Cookie": await commitSession(session),
},
}
);
}
// 有効なセッションが存在する場合 → データを表示
return json({
message: `Showing Session info: ${myStoredData}`,
});
};
export default function Index() {
const data = useLoaderData();
return {data.message};
}
この例では、ユーザーのセッション状態(セッションあり/なし)の両方をハンドリングしています。セッションがない場合は新規作成してダミーデータを格納し、有効なセッションがある場合はそのデータを表示します。
ステップ4:デプロイ
Upstashを使ったセッション実装が完了したら、どのデプロイ戦略も自由に選択できます。
デプロイ先の環境変数に Upstash の認証情報(
UPSTASH_REDIS_REST_URL、UPSTASH_REDIS_REST_TOKEN、SESSION_SECRET)を設定するのを忘れないでください。
付録:ローカル開発と本番でストレージを切り替える
オフライン環境でも開作業を進められるよう、ローカル開発時はファイルシステムベースの createFileSessionStorage を、ステージング/本番では createUpstashSessionStorage を使い分けるのが一般的です。NODE_ENV で環境を判定して切り替えます。
Upstash実装の動作確認が取れたら、sessions.server.ts を以下のように書き換えます:
// sessions.server.ts
import { createCookie, createSessionStorage } from "@remix-run/node";
import { createFileSessionStorage } from "@remix-run/node";
// createUpstashSessionStorage のインポートはそのまま
import { createUpstashSessionStorage } from "~/sessions/upstash.server";
const EXPIRATION_DURATION_IN_SECONDS = 60 * 60 * 24;
const expires = new Date();
expires.setSeconds(expires.getSeconds() + EXPIRATION_DURATION_IN_SECONDS);
const sessionCookie = createCookie("__session", {
secrets: [process.env.SESSION_SECRET || "r3m1xr0ck1"],
sameSite: "lax",
expires,
httpOnly: true,
secure: process.env.NODE_ENV === "production",
path: "/",
});
// 環境に応じてストレージを切り替え
const { getSession, commitSession, destroySession } =
process.env.NODE_ENV === "development"
? createFileSessionStorage({ cookie: sessionCookie, dir: "./sessions" })
: createUpstashSessionStorage({ cookie: sessionCookie });
export { getSession, commitSession, destroySession };
これでローカル開発時は ./sessions ディレクトリのファイルシステムを使用し、本番環境では自動的にUpstash Redisが使用されるようになります。
まとめ
この記事では、Upstash RedisをRemixのセッションストレージとして活用する方法を解説しました。Remixの createSessionStorage APIは、具体的なストレージ実装を見事にカプセル化しており、統合を非常にシンプルにしています。実際の動作例は Remix公式リポジトリの例 で確認できます。
快適なRemixライフを!
-
RemixとサーバーレスRedis(Upstash)でTODOアプリを作成する方法
この記事では、RemixとサーバーレスRedis(Upstash)を組み合わせて、シンプルなTODOアプリを作成する手順を解説します。 Remixは、ユーザーインターフェースに集中しながら、Webの基本原則に立ち返ることで、高速で滑らか、そして堅牢なユーザーエクスペリエンスを提供できるフルスタックWebフレームワークです。 Remixプロジェクトの作成 まず、以下のコマンドを実行します。 npx create-remix@latest プロジェクトの雛形が完成しました。続いて、依存パッケージをインストールし、開発サーバーを起動します。 npm install npm run dev ユーザーイ
-
Upstash Redisで実現するNetlify Graphのグローバルキャッシュ
はじめに先日、Netlifyは「Netlify Graph」という新機能を発表しました。同僚が以前から指摘していた不足していたピースに対して、Netlifyがソリューションへ向けて良い一歩を踏み出した形です。Netlify Graphは、開発者がWebアプリ向けのGraphQL API呼び出しを構築するのを支援する機能です。Netlifyダッシュボード上でGraphQLリクエストを準備すれば、ワンクリックでクライアントコードをプロジェクトに注入できます。Netlify Functionsとサードパーティサービスの課題Netlify Functionsをサードパーティサービスと組み合わせて使う場