Next.jsとUpstashでセッション管理をしてみる【Redis】
こんにちは。
本日はNext.jsでクッキーを使用したセッション管理を実践してみたいと思います。
なお、認証に関連するセッションについては、各認証サービスの公式ドキュメントを参照してください。
まずは、簡単にセッションの概念について説明します。
1.セッションとは?
PHPやRubyなどのサーバーサイド言語を扱う場合、セッション管理は当たり前のように行われることです。
セッションは、サーバー側にデータを保存する仕組みです。
簡単に言えば、サーバーがユーザーの情報や状態を記憶しておくためのものです。
しかし、セッションについて理解する前に、ステートについても説明したいと思います。
2.ステートレスとステートフル
ECサイトでは、会員登録していない状態でも商品をカートに追加することができます。
その後、サイトを閉じて再度訪れると、カートには商品が残っています。
このような状態を実現するために、クライアントからの情報をサーバー側で保持し、セッションを管理しています。
つまり、サーバーがクライアントのセッション状態を保持していることで、ステートフルな状態が実現されています。
3.一般的なセッション管理の流れ
では実際にセッション管理をする方法について説明します。
セッションを管理する方法はさまざまですが、一般的な方法は以下の通りです。
4.セッション管理の方法
セッション管理の方法はいくつかありますが、代表的な3つを紹介しましょう。
それぞれの方法にはメリットとデメリットがあります。
今回はセッション管理にRedis(インメモリ方式)を使用します。
5.Next.jsとセッション管理
Next.jsでは、サーバーサイドで処理されるライフサイクルを持つため、セッションを扱うことができます。
6.さっそくやってみる
まず、以下の順序で環境を構築していきます。
7.ライブラリをインストールする
$ yarn add -D nookies nanoid ioredis
8.処理を書く
// utils/redisClient.ts
import Redis from 'ioredis';
//-----------------------------------------------------------------
// ioredisの初期化処理
//-----------------------------------------------------------------
export const redisClient = new Redis(`rediss://:${process.env.REDIS_PASSWORD ?? ''}@${process.env.REDIS_HOST ?? ''}:${process.env.REDIS_PORT ?? ''}`, {
tls: { rejectUnauthorized: false }
});
// pages/index.tsx
import { parseCookies, setCookie } from 'nookies';
import { nanoid } from 'nanoid';
import { redisClient } from 'utils/redisClient';
//-----------------------------------------------------------
// component
//-----------------------------------------------------------
const Index = ({ session, datas, isResetStatus, weeks }: IProps) => {
//-----------------------------------------------------------
// カートに追加するハンドラー
//-----------------------------------------------------------
const onAddCartHandler = async (productId: number, productName: string, productPrice: number) => {
try {
const datas = await fetch('api/v1/card/add', {
method: 'POST',
body: JSON.stringify({
productId, title, price
}),
headers: { 'Content-Type': 'application/json', Accept: '*/*' }
});
alert('カートに追加しました');
} catch (e) {
alert('カート追加に失敗しました');
} finally {
setLoading(false);
}
};
return (
<button
onClick={() => {
onAddCartHandler(9999, 'サンプル商品', 420);
}}
/>
);
}
//-----------------------------------------------------------
// ssr
//-----------------------------------------------------------
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
// contextを渡してcookie内の値を取得する
const cookie = parseCookies(context);
// cookie内のsessionIdがあるかどうかを判定
if(cookie.sessionId) {
// RedisからsessionIdを取得する
const sessionId = await redisClient.hget(cookie.sessionId, 'sessionId');
// Redisから取得できたsessionIdをもとになにか処理する
// ここで実際の処理
} else {
// sessionIdを生成する
const newSessionId = nanoid(35);
// sessionIdをredisに保存してexpiredを7日間に設定する
await this.redisClient.hsetnx(newSessionId, 'sessionId, newSessionId);
await this.redisClient.expire(newSessionId, 60 * 60 * 24 * 7);
// クライアント側のcookieにsessionIdを保存する
setCookie(context, 'sessionId, newSessionId), {
maxAge: 60 * 60 * 24 * 7, // 7日間
httpOnly: true, // trueにしないとJSから書き換えられるのでtrueにする
secure: true, // HTTPSの通信の場合のみ送信されるようにtrueにする
sameSite: 'lax',
path: '/'
});
}
return {
props: {};
}
};
export default Index;
// pages/api/v1/cart/add.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { parseCookies } from 'nookies';
import { redisClient } from 'utils/redisClient';
//-----------------------------------------------------------
// type
//-----------------------------------------------------------
type IBody = {
productId: number;
productName: string;
productPrice: number;
};
//-----------------------------------------------------------
// api
//-----------------------------------------------------------
const Index = async (req: NextApiRequest, res: NextApiResponse) => {
// クライアントからPOSTされてきたBody
const datas = req.body as IBody;
// contextのreqを渡してcookie内の値を取得する
const cookie = parseCookies({ req });
// cookieがなければエラー
if(!cookie) throw new Error('Cookie Parse Error.');
// RedisのsessionにproductIdを追加する
await redisClient.hset(cookie.sessionId, 'productId', datas.productId);
};
export default Index;
処理は大まかに以下の3つに分けられます。
ポイントは、getServerSideProps および API 内で Cookie から sessionId を取り出す部分です。
Cookie の値はリクエストのタイミングでサーバー側に送信されるため、Cookie を解析して sessionId を取得しています。
sessionId が存在する場合は、ユーザーを識別するための特別な処理を挿入することができます。一方、sessionId が存在しない場合は、新たに sessionId を生成します。
cookieにsessionIdのみを格納する方法を採用することで、Redis(インメモリ方式)でsessionIdを保持している間、ユーザーを識別する値を保存することができました。
それでは。
この記事が気に入ったらサポートをしてみませんか?