Back-EndSchemas
Ecommerce Schema
Complete MongoDB schemas for an e-commerce store — Product, Cart, Order, and Review.
Ecommerce Schema
Core schemas for an online store — products with variants, a cart tied to each user, orders with line items and status tracking, and reviews.
Product Schema
// src/models/product.model.js
import mongoose from "mongoose";
const productSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
},
slug: {
type: String,
required: true,
unique: true,
lowercase: true,
},
description: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
min: 0,
},
discountPrice: {
type: Number,
min: 0,
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
},
images: [{ type: String }],
stock: {
type: Number,
required: true,
default: 0,
min: 0,
},
// simple variant support — e.g. size, color
variants: [
{
name: { type: String }, // e.g. "Size"
options: [{ type: String }], // e.g. ["S", "M", "L"]
},
],
averageRating: {
type: Number,
default: 0,
min: 0,
max: 5,
},
numReviews: {
type: Number,
default: 0,
},
isActive: {
type: Boolean,
default: true,
},
},
{ timestamps: true }
);
productSchema.index({ name: "text", description: "text" });
const Product = mongoose.model("Product", productSchema);
export default Product;Cart Schema
// src/models/cart.model.js
import mongoose from "mongoose";
const cartSchema = new mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
unique: true, // one cart per user
},
items: [
{
product: {
type: mongoose.Schema.Types.ObjectId,
ref: "Product",
required: true,
},
quantity: {
type: Number,
required: true,
min: 1,
default: 1,
},
// snapshot price at time of adding — protects against price changes
priceAtAdd: {
type: Number,
required: true,
},
},
],
},
{ timestamps: true }
);
const Cart = mongoose.model("Cart", cartSchema);
export default Cart;Order Schema
// src/models/order.model.js
import mongoose from "mongoose";
const orderSchema = new mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
items: [
{
product: {
type: mongoose.Schema.Types.ObjectId,
ref: "Product",
required: true,
},
name: { type: String, required: true }, // snapshot — survives product deletion
quantity: { type: Number, required: true },
price: { type: Number, required: true }, // price at time of order
},
],
shippingAddress: {
fullName: { type: String, required: true },
street: { type: String, required: true },
city: { type: String, required: true },
postalCode: { type: String, required: true },
country: { type: String, required: true },
phone: { type: String, required: true },
},
paymentMethod: {
type: String,
enum: ["card", "cod", "paypal"],
required: true,
},
isPaid: {
type: Boolean,
default: false,
},
paidAt: Date,
itemsPrice: { type: Number, required: true },
shippingPrice: { type: Number, required: true, default: 0 },
totalPrice: { type: Number, required: true },
status: {
type: String,
enum: ["pending", "processing", "shipped", "delivered", "cancelled"],
default: "pending",
},
},
{ timestamps: true }
);
const Order = mongoose.model("Order", orderSchema);
export default Order;Review Schema
// src/models/review.model.js
import mongoose from "mongoose";
const reviewSchema = new mongoose.Schema(
{
product: {
type: mongoose.Schema.Types.ObjectId,
ref: "Product",
required: true,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
rating: {
type: Number,
required: true,
min: 1,
max: 5,
},
comment: {
type: String,
maxlength: 500,
},
},
{ timestamps: true }
);
// prevent the same user from reviewing the same product twice
reviewSchema.index({ product: 1, user: 1 }, { unique: true });
const Review = mongoose.model("Review", reviewSchema);
export default Review;Order items store a snapshot of name and price instead of only referencing the product. This way, if a product is later deleted or its price changes, past orders still show exactly what the customer paid for.
Summary
Productincludes simple variant support and a text index for searchCartis one-per-user with apriceAtAddsnapshot to protect against price changes while items sit in the cartOrdersnapshots item name and price at the time of purchase — never relies on live product dataReviewhas a compound unique index onproduct + userto prevent duplicate reviews- All monetary fields use
Numberwithmin: 0validation — for production-grade currency handling consider storing amounts in cents as integers