Clerk と Upstash Redis を使用して安全でスケーラブルなセッション ストアを構築する
Redis の主な使用例の 1 つは、ユーザー セッションを保存および管理して、Web アプリケーション内のリクエスト間で状態を維持することです。これはいくつかの方法で実行でき、新しいサーバーレス ツールの一部には、簡単に導入できるオプションが用意されています。
ユーザー セッション データの管理は、さまざまなビジネス アプリケーションにとって重要です。たとえば、パーソナライゼーション プラットフォームは Redis を使用してユーザーの操作や設定を保存し、カスタマイズされたコンテンツや製品の提案を提供できるようにします。ゲームの世界では、Redis はユーザー データの管理を支援し、プレイヤーのやり取りをリアルタイムで追跡することでスムーズなマルチプレイヤー エクスペリエンスを提供します。広告プラットフォームは、セッション データの保存にも Redis を利用しており、これにより広告配信が最適化され、今後のキャンペーンがパーソナライズされます。次の例では、特に e コマース アプリケーションに焦点を当て、Redis を使用してショッピング カートを効果的に管理する方法を検討します。
プロジェクトの説明
このブログ投稿では、Clerk、Next.js、および Upstash Redis を使用して、ショッピング アプリケーションのセッション ストアを構築します。このプロジェクトで実装する機能のリストは次のとおりです。
- ユーザーはサインアップ、サインイン、サインアウトの操作を実行できるようになります。
- 各ユーザーは、独自のショッピング カートに商品を追加したり削除したりできます。
- ユーザーはショッピング カート内の商品数量を更新できるようになります。
このプロジェクトには、QStash および Upstash Ratelimit に関連する他の機能がいくつかあります。
- アプリケーション内の特定のアクションによりイベントが開始され、QStash を介したメールのスケジュール設定が行われます。これらのメールはその後、再送信によって送信されます。たとえば、チェックアウト時に、発送確認メールが 24 時間後に送信されるようにスケジュールされます。同様に、商品を購入した後、一定の時間が経過すると、ユーザーに購入を評価するよう促すメッセージが表示されます。
- ユーザーはアイテムを評価するオプションもあります。すべての評価データは、Upstash Redis 上の適切なデータ構造に細心の注意を払って保存されます。ユーザー インタラクションのバランスの取れたフローを確保し、潜在的な悪用を防ぐために、評価イベントは Upstash Ratelimit のレート制限機能によって管理されます。
デモ
プロジェクトのデプロイされたデモはここで見ることができます。
ここからこのプロジェクトの Github リポジトリにアクセスすることもできます。
Next.js アプリケーションの作成
新しいターミナル ウィンドウを開き、以下のプロンプトを表示してアプリケーションを作成します。
npx create-next-app@latest
これにより、プロジェクト オプションを尋ねられ、Next.js プロジェクト テンプレートが準備できます。
npx create-next-app@latest
Need to install the following packages:
create-next-app@13.4.18
Ok to proceed? (y) y
✔ What is your project named? shopstash
✔ Would you like to use TypeScript? No / -> Yes
✔ Would you like to use ESLint? No / -> Yes
✔ Would you like to use Tailwind CSS? No / -> Yes
✔ Would you like to use `src/` directory? -> No / Yes
✔ Would you like to use App Router? (recommended) No / -> Yes
✔ Would you like to customize the default import alias? -> No / Yes
Creating a new Next.js app in /Users/***/shopstash. 統合事務員
Clerk をプロジェクトに追加するのは非常に簡単です。事前に構築されたコンポーネントとフックには、Clerk の Next.js SDK を使用します。まず、インストールしましょう:
npm install clerk@nextjs
次に、Clerk ダッシュボードでアプリケーションを作成します。選択に基づいて、必要なサインアップ情報の構成を行うことができます。アプリを作成すると、必要な認証情報の入力を求められます。これらを .env.local にコピーします。 ファイル。以下に例を示します。
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_********
CLERK_SECRET_KEY=sk_test_******** また、「.env.local」ファイル内の Clerk のパスも設定します。
.env.localNEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
ここで、アクティブなセッションとユーザー コンテキストを使用するために、ルート レイアウトを <ClerkProvider> でラップします。 。このチュートリアルの奥深くでは、Header も実装します。 コンポーネント。
import { ClerkProvider } from "@clerk/nextjs";
import Header from "./components/Header";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ClerkProvider>
<html lang="en">
<body className="bg-white">
<Header />
<main className="container bg-white">
<div className="flex min-h-screen items-start justify-center ">
<div className="mt-5">{children}</div>
</div>
</main>
</body>
</html>
</ClerkProvider>
);
}
これで、Clerk がプロジェクトにインストールされました。次のステップは、認証の背後にどのページを隠すかを決定することです。この操作は middleware.tsx で実行します。 ファイルはルート フォルダーに配置されます。
import { authMiddleware } from "@clerk/nextjs";
export default authMiddleware({});
export const config = {
matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
}; これにより、アプリケーション全体が保護されます。サインインせずにページにアクセスしようとすると、認証のためにインデックス ページにリダイレクトされます。
この時点で、アプリにはサインアップ ページとサインイン ページが必要です。ヘッダーからこれらのファイルへのナビゲーションを提供し、このコンポーネントはアクティブ ユーザーに基づいてレンダリングされます。アクティブなユーザーがいる場合、ユーザーはサインアウトしてプロフィールを表示できます。それ以外の場合は、サインインとサインアップのルートが表示されます。
アクティブ ユーザーが存在しないヘッダーの状態は次のとおりです。

最後のステップは、ユーザーのアクションに必要なルートを構築することです。このプロジェクトでは、組み込みの Clerk サインアップ/サインイン コンポーネントを使用しますが、独自のユーザー署名フロー用にカスタマイズされたページ デザインを作成することもできます。サインアップのために、app/sign-in/[[...sign-up]]/page.tsx を作成します。 ルート。
import { SignUp } from "@clerk/nextjs";
const SignUpPage = () => {
return (
<>
<SignUp />
</>
);
};
export default SignUpPage; サインアップ ページはサインインとほぼ同じなので、同様のパスに実装します。
app/sign-in/[[...sign-up]]/page.tsximport { SignIn } from "@clerk/nextjs";
const SignInPage = () => {
return (
<>
<SignIn />
</>
);
};
export default SignInPage; 私たちのプロジェクトへの Clerk の統合に成功したことで、私たちはさらに前進する準備が整いました。アクティブ ユーザー機能が導入され、個別のユーザーごとに Redis 上に一意のセッション ストアを確立する準備が整いました。私たちの戦略には、Clerk からユーザー ID を取得し、その後、そのユーザーのセッション データを Upstash Redis に保存することが含まれます。このプロセスを説明するために、ショッピング カートを構築することを考えてみましょう。ここで、個々のセッションはそれぞれのカート アイテム データを保持します。
何よりもまず、カート項目を構成するものを概念化する必要があります。これは、アプリケーションの残りの部分を作成するときの青写真として機能します。アプリケーションにさまざまな項目を追加したい場合は、ChatGPT のようなツールが非常に役立ちます。あるいは、より直接的なアプローチには、この例に関連付けられた GitHub リポジトリからそれらを取得することが含まれます。そしてもちろん、フロントエンドを本当に実現するには、各アイテムに適した画像をデザインまたは調達する必要があります。
public/items.tsxexport const items = [
{
id: 1,
title: "Elegant Leather Watch",
image: "/images/1.png",
description: "A sophisticated leather watch for all occasions.",
company: "Timepiece Creations",
price: 99.99,
},
]; ショッピング カートの実装
一般的なショッピング アプリケーションでは、ユーザーのカートに複数のセクションからアクセスできる必要があります。これにより、カートの内容に基づいて関連コンポーネントをレンダリングできるようになります。たとえば、個々の商品の詳細ページを表示している場合でも、すべての商品のリストを表示している場合でも、商品がすでにカートに入っているかどうかを確認できる必要があります。この例がどのように表示されるかを少しだけ見てみましょう:

これを可能にするために、React Context API を使用して、半世界規模で必要なカート操作 (商品の追加、削除、カートのリセットなど) へのアクセスを提供します。
Upstash Redis への接続を設定するには、UPSTASH_REDIS_REST_URL をコピーします。 と UPSTASH_REDIS_REST_TOKEN コンソールから値を取得し、.env に貼り付けます。 ファイル。
UPSTASH_REDIS_REST_URL=<YOUR_URL>
UPSTASH_REDIS_REST_TOKEN=<YOUR_TOKEN>
カート コンテキストは app/context/CartContext.tsx に配置されます。 ファイル。このコンテキストをメイン アプリケーションにラップして、アプリケーションが提供するメソッドを使用できるようにします。機能の簡単な概要は次のとおりです。
- ユーザーはカートに商品を追加し、数量を調整できます。
- 商品はカートから削除できます。
- カート全体をリセットできます。
- チェックアウト機能もあります。コンテキスト API の全体像を次に示します。各メソッドを段階的に分解して実装していきます。
上位の概要でアルゴリズムがどのように機能するかは次のとおりです。
- カートはセッション ストアとして扱われ、Upstash Redis 上のハッシュに保持されます。このハッシュの一意の識別子はユーザーの ID に基づくため、各ユーザーには
cart:<USER_ID>の形式で名前が付けられたカートが存在します。 . - カート データを Redis ハッシュに保存するときは、アイテム ID をキーとして使用し、各アイテムの数量を値として使用します。 Redis の組み込みコマンドのおかげで、カートの変更が簡単になります。
- クライアント側では、カートはItem オブジェクトの配列で構成される状態として管理されます。ページが読み込まれると、
useEffectフックは Upstash Redis からカート データを取得します。カートに変更があった場合、関連するすべてのコンポーネントが再レンダリングされます。 - Redis の単純なデータ構造により、
addItemの実装が簡素化されます。 とremoveItem機能性。redis.hincrby()をデプロイすることで コマンドを使用すると、商品の追加または削除からカート内の商品数量の調整までのタスクを処理できます。このユーティリティは Redis の優れた能力を強調します。 resetCartの場合 関数では、redis.del()を使用して Upstash データベースからハッシュのキーを削除します。 .
カートの概念的な概要を理解したので、今度は袖をまくって核となるメソッドに取り掛かります。
商品をカートに追加する
Redis ハッシュの観点から見ると、項目の追加または数量の変更には同じコマンドが必要です。 hincrby コマンドはキーを作成してその値を 1 に設定するか、increment に基づいて関連する値を増やします。 コマンドのパラメータ。
クライアント側では、新しいアイテムを導入するか、カートの状態の数量を調整することで、これらのアクションを反映します。
contexts/CartContext.tsxconst addItem = async (id: number) => {
const item = items.find((i) => i.id === id);
if (!item) return;
const doesItemExist = cart.some((i) => {
return id === i.id;
});
let newCart: Item[];
if (!doesItemExist) {
newCart = [...(cart || []), item];
redis.hincrby(`user:${userId}`, id.toString(), 1);
//We create an item in the state object with the given id, and set the quantity to 1.
const newCartItemIDs = { ...cartItems, [id]: 1 };
setCartItemIDs(newCartItemIDs);
setCart(newCart);
} else {
const item = items.find((i) => i.id === id);
//This item currently exists in the state object as key, so we increase the value by 1.
const updatedItemQuantities = {
...cartItems,
[id]: cartItems[id] + 1,
};
setCartItems(updatedItemQuantities);
redis.hincrby(`user:${userId}`, id.toString(), 1);
}
}; カートから商品を削除する
削除は追加操作と似ています。 hincrby を使用してハッシュ値を減らすことができます。 、increment を指定することで パラメータは -1 として指定します .
const removeItem = async (id: number, force: boolean = false) => {
const doesItemExist = cart.some((i) => {
return id === i.id;
});
if (!doesItemExist) return;
if (cartItems[id] === 1 || force) {
const newCart: Item[] = cart.filter((item: { id: number }) => {
return item.id !== id;
});
// Creating the new state object for cart
const newCartItems = { ...cartItems };
delete newCartItems[id];
//Removing the item from Upstash Redis hashset.
redis.hdel(`user:${userId}`, id.toString());
setCart(newCart);
setCartItems(newCartItems);
} else if (cartItems[id] > 1) {
const updatedItemQuantities = {
...cartItems,
[id]: cartItems[id] - 1,
};
setCartItems(updatedItemQuantities);
redis.hincrby(`user:${userId}`, id.toString(), -1);
}
}; カート機能が導入され、すべての重要なメソッドが完成しました。プロジェクト全体にシームレスに統合され、グローバルにアクセスできます。この機能の 2 つの使用例を次に示します。
-
インデックス ページ :ここではすべての項目が表示されます。各商品には固有のボタンが付いており、カートに追加できます。製品がすでにカートに入っている場合は、それを削除するオプションがあります。
-
shadcnuiを使用します。 React UI ライブラリを使用して、カート内のすべてのアイテムを 1 つのページに統合するモーダル/シートを構築します。このスペースは単に閲覧するためのものではありません。必要に応じてアイテムの数量を変更できます。気分が変わりやすいと感じたら、カートをリセットするか、チェックアウトに進むかのオプションがすぐそこにあります。
インデックス ページ
注意点:インデックス ページのアイテムは、サインインしているアクティブなユーザーのみに表示されます。まず、Clerk からユーザー データをフェッチし、Clerk からの応答に基づいてコンポーネントをレンダリングします。
カード コンポーネントでは、必要な関数とオブジェクト CartContext を取得するだけです。
コンポーネント/CardComponent.tsxexport default function CardComponent(props: { item: cardProps }) {
const { item } = props;
const { id, title, image, company } = item;
const { addItem, removeItem, cartItems } = useContext(CartContext);
return (
<>
<Card className="transition duration-200 hover:shadow-lg">
<Link href={`/products/${id}`}>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>
<Image src={image} alt={title} width={300} height={300}></Image>
<CardDescription>{company}</CardDescription>
</CardContent>
</Link>
<CardFooter>
<div className="grid grid-rows-2">
<CartButton
id={id}
cartItems={cartItems}
addItem={addItem}
removeItem={removeItem}
/>
</div>
</CardFooter>
</Card>
</>
);
} ここで重要なコンポーネントは、カートに商品を追加またはカートから削除できるカート ボタンです。このボタンは、カートの現在の状態を使用して表示されます。
コンポーネント/CartButton.tsxconst CartButton = ({
id,
cartItems,
addItem,
removeItem,
}: {
id: number;
cartItems: cartContent;
addItem: (id: number) => Promise<void>;
removeItem: (id: number, force: boolean) => Promise<void>;
}) => {
const itemExists: boolean = cartItems?.hasOwnProperty(id);
const { triggerEvent } = useContext(UserStateContext);
return (
<button
className={`${
itemExists ? "bg-red-400 text-black" : "bg-cyan-500 text-black"
} flex items-center justify-center gap-3 rounded-full px-4 py-2 transition-all duration-300`}
onClick={() => {
if (itemExists) {
removeItem(id, true);
} else {
addItem(id);
}
}}
>
<p className="text-sm font-bold">
{itemExists ? "Remove from Cart" : "Add to Cart"}
</p>
<FaCartShopping size="25" />
</button>
);
}; カートの状態が動的になったので、現在のステータスに基づいて特定のコンポーネントのレンダリングを促すことができます。次に、専用のカート コンポーネントを紹介します。このスペースは、カートの内容全体を表示したり、数量を調整したり、リセット ボタンを押したりするための中心として機能します。
私たちは shadcn/ui シート コンポーネントをベースとして使用し、その内部をパーソナライズする計画を立てています。
カートが空になっているシナリオでは、コンポーネントは状況を明確に示します。

ただし、アイテムがカートに少しずつ入ってくると、このコンポーネントで簡単に表示されるようになります。選択内容を確認できるだけでなく、数量を自由に調整したり、アイテムの総合的な価値を測定したりすることもできます。

このコンポーネントの作成の完全なチュートリアルについては、以下をご覧ください。ボタンの設定などの細かい点を探している場合は、コードベース全体を使用できる GitHub リポジトリにアクセスすることをお勧めします。
この最後のコード スニペットで、私たちのプロジェクトは終了に達します。私たちはコンセプトから実装まで順調に進み、Upstash Redis のパワーと shadcn/ui ライブラリの柔軟性を利用してカート機能を実現しました。
結論
ユーザー管理のための Clerk と効率的なデータストレージのための Upstash Redis の組み合わせは、ダイナミック カート システムの作成に役立ちました。これらが連携してアプリケーションのバックボーンを形成し、セキュリティとパフォーマンスの両方を保証します。このプロジェクトは、Upstash Redis、Clerk などの強力なツールを組み合わせて使用することで、非常に複雑な問題をどのようにスムーズに解決できるかを示す好例です。
このプロジェクトをさらに改善するための提案をいくつか示します。
-
ユーザー エクスペリエンス: 堅牢で機能的なカートを確立しましたが、アニメーション、フィードバック ループ、さらには詳細な製品プレビューなど、ユーザー インターフェースの機能強化をさらに深く掘り下げることで、さらにシームレスなユーザー ジャーニーを提供できる可能性があります。
-
パフォーマンス: Upstash Redis の基本的な使用により、高度なキャッシュ戦略をさらに掘り下げ、ロード時間の短縮とより充実したオフライン エクスペリエンスを実現するために Service Worker を統合することができます。
-
特徴: ウィッシュリスト、現在のカートの内容に基づいてカスタマイズされた製品の推奨、またはプロモーション コードを適用するシステムを使用してカートの機能を拡張すると、ショッピング エクスペリエンスが向上する可能性があります。
-
統合: 決済ゲートウェイを統合してスムーズなチェックアウト プロセスを実現したり、サードパーティの在庫システムや CRM システムと連携して包括的な e コマース ソリューションを実現したりする可能性もあります。
この開発の冒険をフォローしていただきありがとうございます。私たちは皆様からのフィードバックをお待ちしており、この基本的な構造に皆様がもたらす革新を楽しみにしています。このプロジェクトに関してご質問や問題がございましたら、fahreddin@upstash.com までお気軽にご連絡ください。
プロジェクトの Github リポジトリはここにあります。
QStash と再送信を使用して電子メールをスケジュールする部分については、別の投稿で説明します。それまでは、サンプル リポジトリをチェックして実装を確認してください。
-
Lucia、PlanetScale、Upstash Redis を使用した SvelteKit での安全でタイプセーフな認証
Upstash ブログの前回のガイドがBytes ニュースレターに掲載されました。 、SvelteKit パーティーを続けようと思いました。 Svelte の熱烈なファンとして、私は日に日に参加者が増えているのを目にして、将来がとても楽しみです。 まだ目立たないツールの 1 つが Lucia です。 このガイドでは、Lucia で認証を起動して実行する方法を説明します。データベースのニーズには PlanetScale を使用し、セッションの処理には Upstash Redis を使用します。 以下は、このガイドの最終目標のスクリーンショットです。サンプル リポジトリはここにあります。
-
Upstash Redis、Next.js サーバー アクション、Vercel を使用したリアルタイム通知:ステップバイステップ ガイド
この投稿では、Upstash Redis、Next.js Server Actions、Vercel で Server-Sent Events を使用してリアルタイム通知を構築した方法について説明します。 Upstash Redis でメッセージ チャネルを活用すると、アプリケーションの通信アーキテクチャが大幅に強化され、アプリケーションの応答性と動的さが向上します。 デモ 使用するもの Next.js(フロントエンドとバックエンド) Upstash Redis (PUBLISH コマンドを使用したサーバー送信イベント) Tailwind CSS(スタイリング) Vercel(導入)