September 2, 2025 β’ By Shakib Khan
Welcome! You might already know what Redis is. But if you don't, no worries β here's a quick explanation:
Redis is an in-memory keyβvalue database, used as a distributed cache and message broker, with optional durability. Because it holds all data in memory and because of its design, Redis offers low-latency reads and writes, making it particularly suitable for use cases that require a cache.
β Wikipedia
We can use Redis for many tasks like caching, message broker etc.
This blog is about managing cart sessions using Redis. Let's understand it with a short story.
This blog is about managing cart sessions using Redis. Let's understand it with a short story.
Alex visits an e-commerce site to shop. He adds some items to his cart. Later, he realizes he doesn't have enough money, so he leaves without buying anything.
Now you might be wondering β what does this have to do with Redis?
Here's the point:
Even though Alex didn't buy anything, the items he added were marked as "booked" in the inventory. So, one item is gone from the stock β just sitting in his cart.
If it's just one person, it's not a big issue. But imagine 100 users doing the same thing. The inventory would show fewer items, even though no one actually bought them.
So, who will release those items from the cart and return them to the inventory?
This is where Redis session management comes in.
We can use Redis to automatically clear a cart if the user doesn't complete the purchase within a certain time β like 15 minutes or 1 hour. This way, the cart gets released, and the item goes back into stock.
Now you understand why Redis is better than a regular database for handling carts like this β it's fast, lightweight, and perfect for temporary data like sessions.
Now that the explanation is done, let's jump into the programming part.
Run the following command to install the Redis client
yarn add ioredis
yarn add ioredis
You can run Redis using Docker.
It's an easy and fast way to set up Redis without installing it manually on your system.
docker run -p 6969:6969 redis/redis:latest
docker run -p 6969:6969 redis/redis:latest
Once Redis is running, the next step is to connect your Node.js app to it. This allows your app to store and manage session or cart data using Redis.
import { Redis } from "ioredis"; const redis = new Redis({ host: "localhost", port: 6379, });
import { Redis } from "ioredis"; const redis = new Redis({ host: "localhost", port: 6379, });
When a user adds an item to the cart
we will store that item in Redis. This way, the cart data is saved temporarily, and we can manage it easily β like setting a time limit for how long the item stays in the cart.
app.post("/add-to-cart", async (req, res) => { const { product_id, inventory_id, quantity } = req.body; // Try to get the cart session ID from the request headers let cartSessionId = req.headers["cart-session-id"]; if (cartSessionId) { // Check if the session still exists in Redis const exists = await redis.exists(`sessions:${cartSessionId}`); console.log("Session exists:", exists); // If the session has expired or doesn't exist, reset the session ID if (!exists) { cartSessionId = null; } } // If there is no valid session ID, create a new one if (!cartSessionId) { cartSessionId = uuidv4(); // Generate a unique session ID const TTL = 15 * 60; // Set session expiry time: 15 minutes console.log("New session created:", cartSessionId); // Save the session in Redis with a TTL (Time To Live) await redis.setex(`sessions:${cartSessionId}`, TTL, cartSessionId); // Send the new session ID back to the frontend in the response header res.setHeader("cart-session-id", cartSessionId); } // Save the product in the user's cart in Redis await redis.hset( `cart:${cartSessionId}`, // Key for the user's cart product_id, // Field: product ID JSON.stringify({ inventory_id, // Product's inventory ID quantity, // Quantity of the product }) ); // β NOTE: You should also update the inventory in your main database // to reduce the quantity when an item is added to the cart. // (That part is up to you and your database logic.) // Respond to the client return res.status(200).json({ message: "Product added to cart successfully", cartSessionId, }); });
app.post("/add-to-cart", async (req, res) => { const { product_id, inventory_id, quantity } = req.body; // Try to get the cart session ID from the request headers let cartSessionId = req.headers["cart-session-id"]; if (cartSessionId) { // Check if the session still exists in Redis const exists = await redis.exists(`sessions:${cartSessionId}`); console.log("Session exists:", exists); // If the session has expired or doesn't exist, reset the session ID if (!exists) { cartSessionId = null; } } // If there is no valid session ID, create a new one if (!cartSessionId) { cartSessionId = uuidv4(); // Generate a unique session ID const TTL = 15 * 60; // Set session expiry time: 15 minutes console.log("New session created:", cartSessionId); // Save the session in Redis with a TTL (Time To Live) await redis.setex(`sessions:${cartSessionId}`, TTL, cartSessionId); // Send the new session ID back to the frontend in the response header res.setHeader("cart-session-id", cartSessionId); } // Save the product in the user's cart in Redis await redis.hset( `cart:${cartSessionId}`, // Key for the user's cart product_id, // Field: product ID JSON.stringify({ inventory_id, // Product's inventory ID quantity, // Quantity of the product }) ); // β NOTE: You should also update the inventory in your main database // to reduce the quantity when an item is added to the cart. // (That part is up to you and your database logic.) // Respond to the client return res.status(200).json({ message: "Product added to cart successfully", cartSessionId, }); });
hset
).I've added comments to the code so you can understand it more easily and see what each line does.
Now that our main task is done, we also need a way to get the cart details.
But how do we do that?
Let's find out.
app.get("/get-cart", async (req, res) => { // Get the cart session ID from request headers // You should send this from the frontend (maybe saved in localStorage or cookies) const cartSessionId = req.headers["cart-session-id"]; // If there is no session ID, it means the user has no cart if (!cartSessionId) { return res.status(400).json({ data: [], }); } // Try to fetch the cart data from Redis using the session ID const cart = await redis.hgetall(`cart:${cartSessionId}`); // If the cart is empty or doesn't exist if (!cart || Object.keys(cart).length === 0) { return res.status(404).json({ message: "Cart is empty", data: [], }); } // Convert the cart object into an array of items const cartItems = Object.entries(cart).map(([product_id, value]) => { const { inventory_id, quantity } = JSON.parse(value); return { product_id, inventory_id, quantity }; }); // Return the cart items to the frontend return res.status(200).json({ message: "Cart retrieved successfully", data: cartItems, }); });
app.get("/get-cart", async (req, res) => { // Get the cart session ID from request headers // You should send this from the frontend (maybe saved in localStorage or cookies) const cartSessionId = req.headers["cart-session-id"]; // If there is no session ID, it means the user has no cart if (!cartSessionId) { return res.status(400).json({ data: [], }); } // Try to fetch the cart data from Redis using the session ID const cart = await redis.hgetall(`cart:${cartSessionId}`); // If the cart is empty or doesn't exist if (!cart || Object.keys(cart).length === 0) { return res.status(404).json({ message: "Cart is empty", data: [], }); } // Convert the cart object into an array of items const cartItems = Object.entries(cart).map(([product_id, value]) => { const { inventory_id, quantity } = JSON.parse(value); return { product_id, inventory_id, quantity }; }); // Return the cart items to the frontend return res.status(200).json({ message: "Cart retrieved successfully", data: cartItems, }); });
Isn't it easy?
Once you understand the main concepts, implementing this becomes much simpler. Using this approach, the cart session will automatically expire after 15 minutes.
But have you noticed any problem?
Yes, there is one. When the session expires after 15 minutes, what happens to the inventory quantity? We reduced it when the item was added to the cart, but nowβhow do we add it back?
Fortunately, there is a solution. When the 15-minute session expires, we can trigger a function to return those items to the inventory table. Doesn't that sound like a good idea?
Let's go ahead and implement it.
import { Redis } from "ioredis"; // Create a new Redis client connected to localhost on default port 6379 const redis = new Redis({ host: "localhost", port: 6379, }); // This is the special Redis channel that sends notifications when keys expire const CHANNEL_KEY = "__keyevent@0__:expired"; // Enable Redis keyspace notifications for expired events redis.config("SET", "notify-keyspace-events", "Ex"); // Subscribe to the expired keys notification channel redis.subscribe(CHANNEL_KEY, (err, count) => { if (err) { console.error("Failed to subscribe: ", err); } else { console.log( `Subscribed successfully! This client is currently subscribed to ${count} channel(s).` ); } }); // Listen for messages from the subscribed channel redis.on("message", async (channel, message) => { // Check if the message is from the expired key channel if (channel === CHANNEL_KEY) { console.log(`Key expired: ${message}`); // Extract the cart session ID from the expired key message // Assumes the key format is like "sessions:<cart_id>" const cart_id = message.split(":").pop(); if (!cart_id) { console.log("No cart ID found in the expired key message."); return; // Stop if no cart ID found } console.log(`Cart session expired: ${cart_id}`); // Delete the cart data from Redis since the session expired await redis.del(`cart:${cart_id}`); console.log(`Cart data for session ${cart_id} deleted.`); // TODO: Add logic here to increase the quantity of the items back to the inventory // This part depends on your inventory database and setup console.log("Items added back to the inventory."); } });
import { Redis } from "ioredis"; // Create a new Redis client connected to localhost on default port 6379 const redis = new Redis({ host: "localhost", port: 6379, }); // This is the special Redis channel that sends notifications when keys expire const CHANNEL_KEY = "__keyevent@0__:expired"; // Enable Redis keyspace notifications for expired events redis.config("SET", "notify-keyspace-events", "Ex"); // Subscribe to the expired keys notification channel redis.subscribe(CHANNEL_KEY, (err, count) => { if (err) { console.error("Failed to subscribe: ", err); } else { console.log( `Subscribed successfully! This client is currently subscribed to ${count} channel(s).` ); } }); // Listen for messages from the subscribed channel redis.on("message", async (channel, message) => { // Check if the message is from the expired key channel if (channel === CHANNEL_KEY) { console.log(`Key expired: ${message}`); // Extract the cart session ID from the expired key message // Assumes the key format is like "sessions:<cart_id>" const cart_id = message.split(":").pop(); if (!cart_id) { console.log("No cart ID found in the expired key message."); return; // Stop if no cart ID found } console.log(`Cart session expired: ${cart_id}`); // Delete the cart data from Redis since the session expired await redis.del(`cart:${cart_id}`); console.log(`Cart data for session ${cart_id} deleted.`); // TODO: Add logic here to increase the quantity of the items back to the inventory // This part depends on your inventory database and setup console.log("Items added back to the inventory."); } });
This code listens for Redis key expiration events to manage cart sessions:
This approach ensures that when cart sessions expire, the reserved inventory is automatically released, maintaining accurate stock levels without any manual effort.
Let me break down what each component represents and how data flows through the system:
π΅ Frontend Layer:
π’ Backend API Layer:
π‘ Redis Database Layer:
π Main Database Layer:
1. Adding Items to Cart:
2. Viewing Cart:
3. Session Expiry Process:
That's all for now.
This is a basic implementation, but you can expand it further by adding more advanced logic or creating new features based on this concept.
If you have any questions or notice any mistakes, please feel free to leave a comment below. Your feedback is highly appreciated!
Thank you for reading this blog. See you soon with another exciting topic!
Good code is its own best documentation. β Steve McConnell