Building Session Store with Clerk and Upstash Redis
One of the main use cases of Redis is storing and managing user sessions to maintain the state across requests in web applications. This can be done in several ways, and some of the newer serverless tools offer easy-to-deploy options.
User session data management is critical for various business applications. For example, personalization platforms use Redis to store user interactions and preferences, enabling them to offer customized content or product suggestions. In the gaming world, Redis helps manage user data to provide a smooth multiplayer experience by tracking player interactions in real-time. Advertising platforms also rely on Redis to store session data, which optimizes ad delivery and personalizes future campaigns. In the example that follows, we'll specifically focus on an e-commerce application and explore how Redis can be used to manage shopping carts effectively.
Project Description
In this blogpost, we will use Clerk, Next.js and Upstash Redis to build a session store for a shopping application. Here is the list of the features we will implement in this project:
- Users will be able to perform sign-up, sign-in and sign-out actions.
- Each user can add, remove items to their unique shopping carts.
- Users will be able to update the item quantities in the shopping cart.
This project has some other features related to QStash and Upstash Ratelimit:
- Certain actions within the application will initiate events, leading to the scheduling of emails through QStash. These emails will subsequently be dispatched by Resend. For instance, upon checking out, a shipping confirmation email will be scheduled for 24 hours later. Similarly, after purchasing an item, users will receive a prompt after a certain time-delay, encouraging them to rate their purchase.
- Users also have the option to rate items. All rating data is meticulously stored in appropriate data structures on Upstash Redis. To ensure a balanced flow of user interactions and prevent any potential misuse, the rating event is governed by the rate-limiting capabilities of Upstash Ratelimit.
Demo
You can see the deployed demo of the project here.
You can also reach the Github repository of this project here.
Creating the Next.js Application
Open a new terminal window, and create the application with prompt below:
npx create-next-app@latest
This will ask you for the project options, and you'll have your Next.js project template ready.
Integrating Clerk
Adding Clerk to a project is fairly simple. We'll use Clerk's Next.js SDK for prebuilt components and hooks. First, lets install it:
npm install clerk@nextjs
Then, we'll create our application on Clerk dashboard. You can do the the configuration for required sign-up info, based on your choice. Once you create the app, the necessary credentials will be prompted. We'll copy these to .env.local
file. Here's an example:
We'll also configure the paths for the Clerk in the '.env.local' file.
Now, to use the active session and user contexts, we'll wrap the root layout with <ClerkProvider>
. Deeper into this tutorial, we'll also implement the Header
component.
Now, Clerk is installed in our project. The next step is to decide which pages to hide behind the authentication. We'll perform this operation in the middleware.tsx
file placed in the root folder.
With this, the whole application is protected. If you try to access any page without signing in, you'll be redirected to the index page for authentication.
At this point, our app needs the sign-up and sign-in pages. We're going to provide the navigation to these files from the Header and this component will be rendered based on the active user. If there's an active user, then user will be able to sign-out and see their profile. Else, sign-in and sign-up routes will be shown.
Here's the state of the header without an active user:
The final step is to build the necessary routes for the user actions. For this project, we'll use the builtin Clerk sign-up/sign-in components, but you can also create customized page design for unique user signing flows. For sign-up, we're going to create the app/sign-in/[[...sign-up]]/page.tsx
route.
Sign up page is almost identical to sign in, and we'll implement it in a similar path.
With the successful integration of Clerk into our project, we are now primed to advance further. The active user feature is in place, setting the stage for us to establish a unique session store on Redis for each distinct user. Our strategy involves retrieving the user ID from Clerk and subsequently storing the session data for said user on Upstash Redis. To illustrate the process, let's consider building a shopping cart. Here, every individual session will retain its respective cart item data.
First and foremost, we need to conceptualize what constitutes a cart item. This will serve as our blueprint as we craft the rest of the application. If you're looking to populate your application with a diverse range of items, tools like ChatGPT can be invaluable. Alternatively, a more direct approach would involve sourcing them from the GitHub repository associated with this example. And, of course, to truly bring your frontend to life, you'll need to design or source suitable image for each item.
Implementing Shopping Cart
In a typical shopping application, a user's cart should be accessible from multiple sections. This allows the relevant components to render based on the cart's contents. For instance, whether you're on an individual item's detail page or viewing a list of all items, you should be able to see if a product is already in your cart. Here's a sneak peek of how our example will appear:
To make this possible, we're going to use the React Context API, offering access to necessary cart operations (like adding items, removing them, or resetting the cart) on a semi-global scale.
To set up the connection to Upstash Redis, we'll copy UPSTASH_REDIS_REST_URL
and UPSTASH_REDIS_REST_TOKEN
values from the Console and paste them into your .env
file.
Our Cart Context will be placed in the app/context/CartContext.tsx
file. We'll wrap this context around the main application, enabling us to use the methods it provides. Here's a quick rundown of the capabilities:
- Users can add items to their cart and adjust quantities.
- Items can be removed from the cart.
- Entire carts can be reset.
- There's also a checkout feature. Here's an overarching view of the context API. We'll break down and implement each method step by step.
Here's how the algorithm works in the higher overview.
- The cart, treated as a session store, will be held in a hash on Upstash Redis. The unique identifier for this hash will be based on the user's ID, so each user will have a cart named in the format of
cart:<USER_ID>
. - When saving cart data to the Redis hash, we'll use item IDs as keys and the quantity of each item as values. Thanks to Redis's built-in commands, modifying the cart becomes a breeze.
- On the client side, the cart will be managed as a state consisting of an array of Item objects. When a page is loaded, the
useEffect
hook fetches the cart data from Upstash Redis. Should there be any alterations to the cart, all relevant components get rerendered. - Redis's straightforward data structures simplify the implementation of
addItem
andremoveItem
functionalities. By deploying theredis.hincrby()
command, we can handle tasks ranging from adding or removing items to adjusting item quantities within the cart. This utility underscores Redis's prowess. - For the
resetCart
function, we'll just delete the hash's key from the Upstash database usingredis.del()
.
Now that you've grasped the cart's conceptual outline, it's time to roll up our sleeves and get into the core methods.
Adding Item to Cart
From Redis hash's viewpoint, adding an item or changing its quantity requires the same command. The hincrby
command either creates the key and sets its value to 1 or increases the related value based on the increment
parameter of command.
On the client-side, we'll mirror these actions by either introducing a fresh item or tweaking the quantity in the cart's state.
Removing Item from the Cart
Removing is similar to the adding operation. You can decrease a hash value using hincrby
, by giving the increment
parameter as -1
.
The cart functionality is now in place, complete with all essential methods. It's seamlessly integrated throughout the project with global access. Here's a look at two illustrative use cases for this feature:
-
Index Page: Here, all the items are displayed. Unique buttons accompany each item, letting you add them to your cart. If a product is already nestled in your cart, you'll have the option to remove it.
-
We'll use
shadcnui
React UI library, and build a modal/sheet that consolidates all the items in the cart on single page. This space isn't just for browsing; you can modify item quantities as needed. And if you're feeling changeable, the options to reset the cart or proceed to checkout are right there.
Index Page
A point of note: items on the index page are showcased exclusively for active, signed-in users. First, we fetch the user data from Clerk and render the components based on the response from Clerk.
In the card component, we'll just retrieve the necessary functions and objects CartContext.
The important component here is the cart button, where you can add or remove the item from the cart. This button will be rendered using the current state of the cart.
Now that our cart's state is dynamic, it can prompt specific component renderings based on its current status. Next, we'll introduce a dedicated cart component. This space will serve as the epicenter for viewing the entirety of the cart's contents, making quantity adjustments, or hitting the reset button.
We're turning to the shadcn/ui sheet component as our base, with plans to personalize its interior.
In scenarios where the cart stands vacant, the component paints a clear picture of the situation:
However, as items trickle into the cart, they become readily visible in this component. Not only can you see your selections, but you also have the freedom to tweak quantities or gauge the collective value of your items.
For a full walkthrough of this component's creation, take a look below. If you're hunting for minutiae, like button configurations, I'd recommend heading over to our GitHub repository, where the entirety of the codebase is at your use.
With that final snippet of code, our project reaches its conclusion. We've successfully journeyed from concept to implementation, bringing our cart feature to life using the power of Upstash Redis and the flexibility of the shadcn/ui library.
Conclusion
The combination of Clerk for user management and Upstash Redis for efficient data storage has been instrumental in the creation of our dynamic cart system. Together, they've formed the backbone of our application, ensuring both security and performance. This project is a great example of how these powerful tools such as Upstash Redis, Clerk used together solves very complex problems smoothly.
Here are some suggestions for further improvements on this project:
-
User Experience: While we've established a robust and functional cart, delving deeper into user interface enhancements—animations, feedback loops, or even detailed product previews—could provide an even more seamless user journey.
-
Performance: With the foundational use of Upstash Redis, we can further delve into advanced caching strategies, perhaps integrating service workers for improved load times and a richer offline experience.
-
Features: Expanding the cart's capabilities with wishlists, tailored product recommendations based on current cart contents, or a system for applying promotional codes could elevate the shopping experience.
-
Integration: There's potential to integrate payment gateways for a smooth checkout process or even interface with third-party inventory or CRM systems for a comprehensive eCommerce solution.
Thank you for following along this development adventure. We're eager to hear your feedback and would love to see the innovations you might bring to this foundational structure! Should you have any questions or problems about this project, feel free to get in contact with me at fahreddin@upstash.com.
You can find the Github Repository of the project here.
The part where we schedule emails using QStash and Resend will be covered in another post. Until then you can check the example repo to see the implementation.