Introduction
In this lab, you will learn how to design a MongoDB schema for an e-commerce application. The goal is to create a single, comprehensive document for each order that is easy to manage and query. You will start by creating a basic order structure, then progressively enrich it by embedding detailed customer information, a list of ordered items, payment details, and a status history. This approach demonstrates the power of MongoDB's document model for real-world applications.
Create a Basic Order Document
In this first step, you will connect to MongoDB and create a new database. Then, you will insert your first order document, which will serve as the foundation for the schema we will build upon in the following steps.
First, open the MongoDB Shell. This interactive command-line interface allows you to communicate with your MongoDB database.
mongosh
Once inside the shell, you will see a > prompt. Now, switch to a new database named ecommerce. If the database does not exist, MongoDB will create it for you when you first store data.
use ecommerce
Next, you will create a collection named orders by inserting a document into it. This document will represent a single order and will contain essential information like an order ID, customer details, the date of the order, and its current status.
Execute the following command to insert the document:
db.orders.insertOne({
order_id: "ORD001",
order_date: new Date("2023-10-26T10:00:00Z"),
customer_id: "CUST123",
status: "pending",
total: 150.0
});
This command performs the following actions:
db.orders: Specifies theorderscollection within the current database.insertOne(): A MongoDB method to insert a single document.- The document itself is a JSON-like object with key-value pairs for
order_id,order_date,customer_id,status, andtotal.
After successful insertion, MongoDB will return an acknowledgment along with the unique _id of the newly created document.
{
"acknowledged": true,
"insertedId": ObjectId("...")
}
You have now created the basic structure for an order. In the next step, you will enhance this document with more detailed information.
Enhance with Embedded Customer Details
A simple customer_id is often not enough. In a real application, you would frequently need the customer's name and address when retrieving an order. Instead of performing a separate query to a customers collection, we can embed this information directly within the order document. This is a common and powerful pattern in MongoDB that improves read performance.
In this step, you will update the order document to include detailed, nested customer information. If you exited the mongosh shell in the previous step, please start it again and run use ecommerce.
Use the updateOne() method to find the document with order_id: "ORD001" and modify it. The $set operator replaces the value of a field with the specified value, and $unset removes a field entirely.
db.orders.updateOne(
{ order_id: "ORD001" },
{
$set: {
customer: {
customer_id: "CUST123",
first_name: "John",
last_name: "Doe",
email: "john.doe@example.com",
shipping_address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
zip_code: "12345"
}
}
},
$unset: {
customer_id: ""
}
}
);
In this command:
- The first argument
{ order_id: "ORD001" }is the filter to select the document to update. - The second argument contains the update operators:
$set: We are setting a new fieldcustomerwhich is an embedded document containing detailed information.$unset: We are removing the old top-levelcustomer_idfield to avoid data redundancy.
To verify your changes, you can retrieve the updated document using findOne():
db.orders.findOne({ order_id: "ORD001" });
The output will now show the nested customer document, and the top-level customer_id field will be gone. This embedded structure keeps related data together in a single document.
Add an Array of Order Items
An order typically consists of one or more products. The best way to model this one-to-many relationship within a single order document is to use an array of embedded documents. Each element in the array will represent an item in the order.
Let's update our order document to include a list of items. We will add an items field, which will be an array. Each object in the array will contain details about a product, such as its ID, name, price, and quantity.
Execute the following updateOne command:
db.orders.updateOne(
{ order_id: "ORD001" },
{
$set: {
items: [
{
product_id: "PROD01",
name: "Laptop",
price: 1200.0,
quantity: 1
},
{
product_id: "PROD02",
name: "Mouse",
price: 25.0,
quantity: 1
}
],
total: 1225.0
}
}
);
Here, we use $set again to add the items array. We also update the total field to match the sum of the item prices. Storing the calculated total directly in the document is another performance optimization, as it avoids the need for aggregation on every read.
Let's check the document again to see the new items array.
db.orders.findOne({ order_id: "ORD001" });
You will see the items array embedded within the order document. This design allows you to retrieve a complete order, including all its items, with a single database query.
Embed Payment Information
Payment details are another critical part of an order. Similar to customer information and items, payment data can be embedded directly into the order document. This includes the payment method, transaction ID, and status.
In this step, you will add a payment sub-document to the order.
db.orders.updateOne(
{ order_id: "ORD001" },
{
$set: {
payment: {
method: "credit_card",
transaction_id: "TXN54321",
status: "completed"
}
}
}
);
This command adds a payment object with three fields: method, transaction_id, and status. Embedding this information ensures that all data related to a single transaction is located in one place.
Let's view the structure of our order document now.
db.orders.findOne({ order_id: "ORD001" });
The document now contains comprehensive information about the order, customer, items, and payment, all accessible through a single read operation.
Track Order Status History
An order progresses through various stages, such as "pending", "processing", "shipped", and "delivered". While the top-level status field shows the current state, it is often useful to maintain a log of all status changes. You can achieve this by adding a status_history array.
In this final step, you will update the order status to "processing" and begin building the status_history array. The $push operator is used to append a value to an array.
First, let's add the initial "pending" status to the history and update the current status to "processing".
db.orders.updateOne(
{ order_id: "ORD001" },
{
$set: { status: "processing" },
$push: {
status_history: {
status: "pending",
timestamp: new Date("2023-10-26T10:00:00Z")
}
}
}
);
Now, let's add the new "processing" status to the history to keep the log complete.
db.orders.updateOne(
{ order_id: "ORD001" },
{
$push: {
status_history: {
status: "processing",
timestamp: new Date("2023-10-26T11:30:00Z")
}
}
}
);
This approach provides a complete audit trail of the order's lifecycle. You can query the document to see its full history at any time.
db.orders.findOne({ order_id: "ORD001" });
The document now includes a status_history array, giving you a complete view of the order's journey. To exit the MongoDB shell, type exit and press Enter.
Summary
In this lab, you have learned how to design a practical and efficient MongoDB schema for an e-commerce order. You started with a basic document and progressively enriched it by embedding related data. You have successfully created a single document that contains customer details, an array of order items, payment information, and a complete status history. This embedded document pattern is a core concept in MongoDB schema design that helps create performant and scalable applications by reducing the need for complex joins and multiple queries.

