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-apicd ecommerce-apinpm init -ynpm install express mongoose dotenv corsnpm install --save-dev nodemonStep 2: Environment Variables
Create `.env`:
PORT=3000MONGODB_URI=mongodb://localhost:27017/ecommerceStep 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
