Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

Clerk 및 Upstash Redis를 사용하여 안전하고 확장 가능한 세션 저장소 구축

Redis의 주요 사용 사례 중 하나는 웹 애플리케이션의 요청 전반에 걸쳐 상태를 유지하기 위해 사용자 세션을 저장하고 관리하는 것입니다. 이는 여러 가지 방법으로 수행할 수 있으며 일부 최신 서버리스 도구는 배포하기 쉬운 옵션을 제공합니다.

사용자 세션 데이터 관리는 다양한 비즈니스 애플리케이션에 매우 중요합니다. 예를 들어, 개인화 플랫폼은 Redis를 사용하여 사용자 상호 작용과 기본 설정을 저장하여 맞춤형 콘텐츠나 제품 제안을 제공할 수 있습니다. 게임 세계에서 Redis는 플레이어 상호 작용을 실시간으로 추적하여 원활한 멀티플레이어 경험을 제공하기 위해 사용자 데이터를 관리하는 데 도움을 줍니다. 광고 플랫폼은 또한 Redis를 사용하여 세션 데이터를 저장하여 광고 게재를 최적화하고 향후 캠페인을 개인화합니다. 다음 예에서는 특히 전자 상거래 애플리케이션에 중점을 두고 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의 Next.js SDK를 사용합니다. 먼저 설치해 보겠습니다:

npm install clerk@nextjs

그런 다음 Clerk 대시보드에 애플리케이션을 생성하겠습니다. 선택에 따라 필수 가입 정보에 대한 구성을 수행할 수 있습니다. 앱을 생성하면 필요한 자격 증명이 표시됩니다. 이를 .env.local에 복사하겠습니다. 파일. 예를 들면 다음과 같습니다:

.env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_********
CLERK_SECRET_KEY=sk_test_********

또한 '.env.local' 파일에서 Clerk의 경로를 구성합니다.

.env.local
NEXT_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도 구현하겠습니다. 구성요소입니다.

레이아웃.tsx
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에서 수행하겠습니다. 파일은 루트 폴더에 저장됩니다.

middleware.tsx
import { authMiddleware } from "@clerk/nextjs";
 
export default authMiddleware({});
 
export const config = {
 matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};

이를 통해 전체 애플리케이션이 보호됩니다. 로그인하지 않고 페이지에 액세스하려고 하면 인증을 위해 색인 페이지로 리디렉션됩니다.

이 시점에서 우리 앱에는 가입 및 로그인 페이지가 필요합니다. 헤더에서 이러한 파일에 대한 탐색 기능을 제공할 것이며 이 구성 요소는 활성 사용자를 기반으로 렌더링됩니다. 활성 사용자가 있는 경우 해당 사용자는 로그아웃하고 자신의 프로필을 볼 수 있습니다. 그렇지 않으면 로그인 및 가입 경로가 표시됩니다.

활성 사용자가 없는 헤더의 상태는 다음과 같습니다.

Clerk 및 Upstash Redis를 사용하여 안전하고 확장 가능한 세션 저장소 구축

마지막 단계는 사용자 작업에 필요한 경로를 구축하는 것입니다. 이 프로젝트에서는 내장된 사무원 가입/로그인 구성 요소를 사용하지만 고유한 사용자 서명 흐름을 위해 사용자 정의된 페이지 디자인을 만들 수도 있습니다. 가입을 위해 app/sign-in/[[...sign-up]]/page.tsx를 생성하겠습니다. 경로.

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.tsx
import { SignIn } from "@clerk/nextjs";
 
const SignInPage = () => {
 return (
 <>
 <SignIn />
 </>
 );
};
export default SignInPage;

Clerk를 우리 프로젝트에 성공적으로 통합함으로써 우리는 이제 더 발전할 준비가 되었습니다. 활성 사용자 기능이 준비되어 각 개별 사용자에 대해 Redis에 고유한 세션 저장소를 설정할 수 있는 단계가 설정되었습니다. 우리의 전략에는 Clerk에서 사용자 ID를 검색한 후 해당 사용자에 대한 세션 데이터를 Upstash Redis에 저장하는 것이 포함됩니다. 프로세스를 설명하기 위해 장바구니 구축을 고려해 보겠습니다. 여기에서는 모든 개별 세션이 해당 장바구니 항목 데이터를 유지합니다.

가장 먼저 장바구니 항목을 구성하는 요소가 무엇인지 개념화해야 합니다. 이는 나머지 애플리케이션을 제작할 때 청사진 역할을 할 것입니다. 다양한 항목으로 애플리케이션을 채우려는 경우 ChatGPT와 같은 도구가 매우 유용할 수 있습니다. 또는 보다 직접적인 접근 방식은 이 예제와 관련된 GitHub 저장소에서 소스를 소싱하는 것입니다. 물론 프런트엔드에 생기를 불어넣으려면 각 항목에 적합한 이미지를 디자인하거나 소싱해야 합니다.

공개/items.tsx
export 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,
 },
];

장바구니 구현

일반적인 쇼핑 애플리케이션에서는 사용자의 카트가 여러 섹션에서 액세스 가능해야 합니다. 이를 통해 관련 구성 요소가 카트 내용을 기반으로 렌더링될 수 있습니다. 예를 들어, 개별 품목의 세부 정보 페이지에 있거나 모든 품목의 목록을 보는 경우 제품이 이미 장바구니에 있는지 확인할 수 있어야 합니다. 다음은 예시가 어떻게 나타나는지 살짝 보여줍니다:

Clerk 및 Upstash Redis를 사용하여 안전하고 확장 가능한 세션 저장소 구축

이를 가능하게 하기 위해 우리는 React Context API를 사용하여 필요한 카트 작업(예:항목 추가, 제거 또는 카트 재설정)에 대한 액세스를 반글로벌 규모로 제공할 것입니다.

Upstash Redis에 대한 연결을 설정하기 위해 UPSTASH_REDIS_REST_URL를 복사하겠습니다. 그리고 UPSTASH_REDIS_REST_TOKEN 콘솔의 값을 .env에 붙여넣으세요. 파일입니다.

.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.tsx
const 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입니다. .

contexts/CartContext.tsx
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);
 }
};

이제 모든 필수 방법이 완비된 카트 기능이 준비되었습니다. 글로벌 액세스를 통해 프로젝트 전반에 걸쳐 원활하게 통합됩니다. 다음은 이 기능에 대한 두 가지 예시적인 사용 사례를 살펴보는 것입니다.

  • 색인 페이지 :여기에는 모든 항목이 표시됩니다. 각 품목에는 고유한 버튼이 있어 장바구니에 추가할 수 있습니다. 제품이 이미 장바구니에 담겨 있는 경우 해당 제품을 제거할 수 있는 옵션이 제공됩니다.

  • shadcnui을 사용하겠습니다. UI 라이브러리를 반응시키고 장바구니에 있는 모든 항목을 단일 페이지에 통합하는 모달/시트를 구축합니다. 이 공간은 단지 탐색만을 위한 공간이 아닙니다. 필요에 따라 품목 수량을 수정할 수 있습니다. 마음이 변하고 싶다면 장바구니를 재설정하거나 결제를 진행하는 옵션이 바로 거기에 있습니다.

색인 페이지

참고 사항:색인 페이지의 항목은 로그인한 활성 사용자에게만 표시됩니다. 먼저 Clerk에서 사용자 데이터를 가져오고 Clerk의 응답을 기반으로 구성요소를 렌더링합니다.

카드 구성 요소에서는 필요한 기능과 CartContext 개체를 검색합니다.

구성 요소/CardComponent.tsx
export 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.tsx
const 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 시트 구성 요소를 기본으로 삼아 내부를 개인화할 계획입니다.

카트가 비어 있는 경우 구성요소는 상황을 명확하게 보여줍니다.

Clerk 및 Upstash Redis를 사용하여 안전하고 확장 가능한 세션 저장소 구축

그러나 항목이 카트에 조금씩 들어가면 이 구성 요소에서 쉽게 볼 수 있습니다. 선택한 항목을 볼 수 있을 뿐만 아니라 수량을 자유롭게 조정하거나 항목의 총 가치를 측정할 수도 있습니다.

Clerk 및 Upstash Redis를 사용하여 안전하고 확장 가능한 세션 저장소 구축

이 구성 요소 생성에 대한 전체 연습을 보려면 아래를 살펴보세요. 버튼 구성과 같은 세부 사항을 찾고 있다면 코드베이스 전체를 사용할 수 있는 GitHub 저장소로 이동하는 것이 좋습니다.

마지막 코드 조각으로 우리 프로젝트는 결론에 도달합니다. 우리는 Upstash Redis의 강력한 기능과 shadcn/ui 라이브러리의 유연성을 사용하여 카트 기능을 생생하게 구현하면서 개념부터 구현까지 성공적으로 진행했습니다.

결론

사용자 관리를 위한 Clerk와 효율적인 데이터 저장을 위한 Upstash Redis의 조합은 동적 카트 시스템을 만드는 데 중요한 역할을 했습니다. 이들은 함께 우리 애플리케이션의 백본을 형성하여 보안과 성능을 모두 보장합니다. 이 프로젝트는 Upstash Redis, Clerk와 같은 강력한 도구를 함께 사용하여 매우 복잡한 문제를 어떻게 원활하게 해결하는지 보여주는 훌륭한 예입니다.

다음은 이 프로젝트의 추가 개선을 위한 몇 가지 제안 사항입니다:

  • 사용자 경험: 강력하고 기능적인 카트를 구축했지만 애니메이션, 피드백 루프 또는 상세한 제품 미리 보기 등 사용자 인터페이스 개선 사항을 더 깊이 탐구하면 훨씬 더 원활한 사용자 여정을 제공할 수 있습니다.

  • 성능: Upstash Redis의 기본 사용을 통해 고급 캐싱 전략을 더욱 깊이 탐구할 수 있으며, 아마도 로드 시간 개선과 더욱 풍부한 오프라인 경험을 위해 서비스 워커를 통합할 수도 있습니다.

  • 특징: 위시리스트, 현재 장바구니 내용을 기반으로 한 맞춤형 제품 추천 또는 프로모션 코드 적용 시스템을 통해 장바구니 기능을 확장하면 쇼핑 경험이 향상될 수 있습니다.

  • 통합: 원활한 결제 프로세스를 위해 결제 대행사를 통합하거나 포괄적인 전자상거래 솔루션을 위해 제3자 재고 또는 CRM 시스템과 인터페이스할 수도 있습니다.

이 개발 모험을 따라와주셔서 감사합니다. 우리는 귀하의 피드백을 듣고 싶어하며 귀하가 이 기본 구조에 가져올 수 있는 혁신을 보고 싶습니다! 이 프로젝트에 대해 질문이나 문제가 있으면 언제든지 fahreddin@upstash.com으로 연락해 주세요.

여기에서 프로젝트의 Github 저장소를 찾을 수 있습니다.

QStash 및 Resend를 사용하여 이메일을 예약하는 부분은 다른 게시물에서 다루겠습니다. 그때까지는 예제 저장소를 확인하여 구현을 확인할 수 있습니다.