How to Build a High-Performance E-commerce Session Cart Using Redis for Fast and Scalable User Experiences

How to Build a High-Performance E-commerce Session Cart Using Redis for Fast and Scalable User Experiences

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.

Imagine This:

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.


Installation

Run the following command to install the Redis client

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

Connecting to Redis

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,
});

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.

Add to Cart Route (Using Redis)

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,
  });
});

πŸ“ Summary (in simple terms):

  • If the user already has a cart session, we continue using it.
  • If the session doesn't exist or has expired, we create a new one.
  • We store the cart and session in Redis with a 15-minute expiration.
  • Each item added to the cart is stored in Redis using a hash (hset).
  • The frontend should store the session ID and send it with future requests.

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.

Get Cart Details (Using Redis)

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,
  });
});

βœ… Summary (In Simple Words):

  • The frontend must send the cart session ID in the headers.
  • If there's no session ID, we assume there's no cart yet.
  • If Redis returns an empty cart, we show a "Cart is empty" message.
  • Otherwise, we parse and return the cart items.

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.

Session Expiry Handler with Inventory Restoration

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.");
  }
});

Summary

This code listens for Redis key expiration events to manage cart sessions:

  • It connects to Redis and subscribes to a special channel that notifies when keys expire.
  • When a cart session key expires (after 15 minutes), the code:
    • Detects the expired session ID.
    • Deletes the corresponding cart data from Redis.
    • Provides a placeholder to restore the inventory quantity by adding the items back.

This approach ensures that when cart sessions expire, the reserved inventory is automatically released, maintaining accurate stock levels without any manual effort.

System Architecture

Diagram Explanation

Let me break down what each component represents and how data flows through the system:

πŸ”΅ Frontend Layer:

  • User Browser (A): The customer's web browser where they interact with your e-commerce site
  • Cart Session ID Storage (B): Where the frontend stores the cart session ID (localStorage, cookies, or session storage)

🟒 Backend API Layer:

  • Add to Cart Route (C): API endpoint that handles adding products to the cart
  • Get Cart Route (D): API endpoint that retrieves current cart contents
  • Session Expiry Handler (E): Background process that manages expired cart sessions

🟑 Redis Database Layer:

  • Session Storage (F): Stores active cart session IDs with 15-minute TTL (Time To Live)
  • Cart Data Storage (G): Hash structure storing actual cart items (product_id, quantity, inventory_id)
  • Keyspace Notifications (H): Redis feature that sends notifications when keys expire

🟠 Main Database Layer:

  • Inventory Table (I): Your main database table tracking product stock levels

Data Flow Explanation

1. Adding Items to Cart:

  • User clicks "Add to Cart" β†’ Browser sends HTTP request to Add to Cart Route (C)
  • Route checks for existing session ID in headers
  • If no session: creates new session in Redis (F) with 15-minute expiry
  • Stores cart item data in Redis hash (G)
  • Reduces inventory quantity in main database (I)
  • Returns session ID to frontend for storage

2. Viewing Cart:

  • User views cart β†’ Browser sends HTTP request to Get Cart Route (D)
  • Route uses session ID from headers to fetch cart data from Redis (G)
  • Returns cart items to frontend for display

3. Session Expiry Process:

  • After 15 minutes, Redis automatically expires the session key (F)
  • Keyspace Notifications (H) detect the expiry and send notification
  • Session Expiry Handler (E) receives the notification
  • Handler deletes expired cart data from Redis (G)
  • Handler restores inventory quantities in main database (I)

Key Benefits of This Architecture:

  • Fast Performance: Redis in-memory storage provides sub-millisecond response times
  • Automatic Cleanup: Sessions expire automatically, no manual intervention needed
  • Inventory Accuracy: Reserved items are automatically returned to stock
  • Scalability: Redis can handle thousands of concurrent cart sessions
  • Real-time Updates: Keyspace notifications ensure immediate response to session expiry

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