Loading

Quipoin Menu

Learn • Practice • Grow

mongodb / E-commerce Cart with Mongoose
tutorial

E-commerce Cart with Mongoose

In this final project, we will build a complete e-commerce shopping cart API. This project demonstrates more complex MongoDB relationships, including embedded documents for cart items and referenced documents for products.

Project Overview: E-commerce Cart API

We''ll build an e-commerce API with:
  • Products catalog (view products by category)
  • Shopping cart (add, update quantity, remove items)
  • Checkout process (create order from cart)
  • Order history

This project brings together everything: data modeling, relationships, complex queries, and real-world business logic.

Step 1: Project Setup
mkdir ecommerce-api
cd ecommerce-api
npm init -y
npm install express mongoose dotenv cors
npm install --save-dev nodemon

Step 2: Environment Variables

Create `.env`:
PORT=3000
MONGODB_URI=mongodb://localhost:27017/ecommerce

Step 3: Create Models

models/Product.js
const mongoose = require('mongoose');

const productSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    trim: true
  },
  description: {
    type: String,
    required: true
  },
  price: {
    type: Number,
    required: true,
    min: 0
  },
  category: {
    type: String,
    required: true,
    enum: ['electronics', 'clothing', 'books', 'home']
  },
  stock: {
    type: Number,
    required: true,
    min: 0,
    default: 0
  },
  imageUrl: String
}, { timestamps: true });

module.exports = mongoose.model('Product', productSchema);

models/Cart.js
const mongoose = require('mongoose');

const cartItemSchema = new mongoose.Schema({
  productId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Product',
    required: true
  },
  quantity: {
    type: Number,
    required: true,
    min: [1, 'Quantity cannot be less than 1']
  },
  price: {
    type: Number,
    required: true
  } <!-- Snapshot of price at add time -->
});

const cartSchema = new mongoose.Schema({
  userId: {
    type: String,
    required: true,
    unique: true
  },
  items: [cartItemSchema],
  total: {
    type: Number,
    default: 0
  },
  updatedAt: {
    type: Date,
    default: Date.now
  }
});

module.exports = mongoose.model('Cart', cartSchema);

models/Order.js
const mongoose = require('mongoose');

const orderItemSchema = new mongoose.Schema({
  productId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Product'
  },
  name: String,
  quantity: Number,
  price: Number
});

const orderSchema = new mongoose.Schema({
  userId: {
    type: String,
    required: true
  },
  items: [orderItemSchema],
  total: {
    type: Number,
    required: true
  },
  status: {
    type: String,
    enum: ['pending', 'paid', 'shipped', 'delivered', 'cancelled'],
    default: 'pending'
  },
  shippingAddress: {
    street: String,
    city: String,
    zip: String,
    country: String
  }
}, { timestamps: true });

module.exports = mongoose.model('Order', orderSchema);

Step 4: Database Connection

Create `config/db.js` (same as previous project).

Step 5: Cart Routes

Create `routes/cart.js`:
const express = require('express');
const Cart = require('../models/Cart');
const Product = require('../models/Product');
const router = express.Router();

<!-- Helper to calculate cart total -->
const calculateTotal = (items) => {
  return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
};

<!-- GET cart for a user -->
router.get('/:userId', async (req, res) => {
  try {
    let cart = await Cart.findOne({ userId: req.params.userId })
      .populate('items.productId');
   
    if (!cart) {
      <!-- Create empty cart if doesn''t exist -->
      cart = new Cart({ userId: req.params.userId, items: [], total: 0 });
    }
   
    res.json(cart);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

<!-- ADD item to cart -->
router.post('/:userId/items', async (req, res) => {
  try {
    const { productId, quantity } = req.body;
   
    <!-- Get product details -->
    const product = await Product.findById(productId);
    if (!product) {
      return res.status(404).json({ message: 'Product not found' });
    }
   
    <!-- Check stock -->
    if (product.stock < quantity) {
      return res.status(400).json({ message: 'Insufficient stock' });
    }
   
    let cart = await Cart.findOne({ userId: req.params.userId });
   
    if (!cart) {
      cart = new Cart({ userId: req.params.userId, items: [] });
    }
   
    <!-- Check if product already in cart -->
    const existingItemIndex = cart.items.findIndex(
      item => item.productId.toString() === productId
    );
   
    if (existingItemIndex > -1) {
      <!-- Update quantity if already in cart -->
      cart.items[existingItemIndex].quantity += quantity;
    } else {
      <!-- Add new item -->
      cart.items.push({
        productId,
        quantity,
        price: product.price
      });
    }
   
    <!-- Recalculate total -->
    cart.total = calculateTotal(cart.items);
    cart.updatedAt = Date.now();
   
    await cart.save();
   
    await cart.populate('items.productId');
    res.json(cart);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

<!-- UPDATE item quantity -->
router.put('/:userId/items/:productId', async (req, res) => {
  try {
    const { quantity } = req.body;
   
    if (quantity < 1) {
      return res.status(400).json({ message: 'Quantity must be at least 1' });
    }
   
    const cart = await Cart.findOne({ userId: req.params.userId });
   
    if (!cart) {
      return res.status(404).json({ message: 'Cart not found' });
    }
   
    const itemIndex = cart.items.findIndex(
      item => item.productId.toString() === req.params.productId
    );
   
    if (itemIndex === -1) {
      return res.status(404).json({ message: 'Item not found in cart' });
    }
   
    cart.items[itemIndex].quantity = quantity;
    cart.total = calculateTotal(cart.items);
    cart.updatedAt = Date.now();
   
    await cart.save();
    await cart.populate('items.productId');
    res.json(cart);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

<!-- REMOVE item from cart -->
router.delete('/:userId/items/:productId', async (req, res) => {
  try {
    const cart = await Cart.findOne({ userId: req.params.userId });
   
    if (!cart) {
      return res.status(404).json({ message: 'Cart not found' });
    }
   
    cart.items = cart.items.filter(
      item => item.productId.toString() !== req.params.productId
    );
   
    cart.total = calculateTotal(cart.items);
    cart.updatedAt = Date.now();
   
    await cart.save();
    await cart.populate('items.productId');
    res.json(cart);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

Step 6: Checkout Route

Create `routes/checkout.js`:
const express = require('express');
const Cart = require('../models/Cart');
const Order = require('../models/Order');
const Product = require('../models/Product');
const router = express.Router();

router.post('/:userId', async (req, res) => {
  try {
    const cart = await Cart.findOne({ userId: req.params.userId })
      .populate('items.productId');
   
    if (!cart || cart.items.length === 0) {
      return res.status(400).json({ message: 'Cart is empty' });
    }
   
    <!-- Check stock for all items -->
    for (const item of cart.items) {
      const product = item.productId;
      if (product.stock < item.quantity) {
        return res.status(400).json({
          message: `Insufficient stock for ${product.name}`
        });
      }
    }
   
    <!-- Create order -->
    const order = new Order({
      userId: req.params.userId,
      items: cart.items.map(item => ({
        productId: item.productId._id,
        name: item.productId.name,
        quantity: item.quantity,
        price: item.price
      })),
      total: cart.total,
      shippingAddress: req.body.shippingAddress
    });
   
    await order.save();
   
    <!-- Update product stock -->
    for (const item of cart.items) {
      await Product.findByIdAndUpdate(item.productId._id, {
        $inc: { stock: -item.quantity }
      });
    }
   
    <!-- Clear the cart -->
    cart.items = [];
    cart.total = 0;
    await cart.save();
   
    res.status(201).json(order);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

Step 7: Server Setup

Create `server.js` similar to blog API, adding the new routes:
app.use('/api/products', productRoutes);
app.use('/api/cart', cartRoutes);
app.use('/api/checkout', checkoutRoutes);
app.use('/api/orders', orderRoutes);

Two Minute Drill

  • We built a complete e-commerce cart API with MongoDB relationships.
  • Used embedded documents for cart items (since they''re tied to the cart).
  • Used referenced documents for products (shared across many carts/orders).
  • Implemented business logic: stock checking, price snapshots, cart totals.
  • Created order from cart with transaction-like consistency.
  • This is a real-world example of MongoDB in e-commerce applications.

Need more clarification?

Drop us an email at career@quipoinfotech.com