This final chapter assembles the course into one service: an order management API built with Express, Mongoose and a MongoDB replica set (Atlas or local). It needs a searchable catalog, order placement that never oversells, per-customer history, a daily sales report and low-stock alerts. After completing it you will have applied modeling, validation, indexes, transactions, aggregation and change streams together in one codebase.
order-api/
src/
db.js connection (mongoose.connect, graceful shutdown)
models/ product.js, customer.js, order.js
routes/ products.js, orders.js, reports.js
jobs/ dailySales.js (aggregation + $merge), lowStockWatcher.js (change stream)Orders reference products and customers but carry a snapshot of the customer contact and each product's name and price at purchase time (extended reference), so history survives later price changes.
const productSchema = new Schema({
sku: { type: String, required: true, unique: true, uppercase: true },
name: { type: String, required: true },
price: { type: Schema.Types.Decimal128, required: true },
stock: { type: Number, required: true, min: 0 },
tags: [String]
});
productSchema.index({ name: "text" });
const orderSchema = new Schema({
customer: { _id: { type: Schema.Types.ObjectId, ref: "Customer", required: true }, name: String, email: String },
items: [{ productId: { type: Schema.Types.ObjectId, required: true }, name: String,
price: Schema.Types.Decimal128, qty: { type: Number, min: 1 } }],
total: { type: Schema.Types.Decimal128, required: true },
status: { type: String, enum: ["placed", "paid", "shipped", "cancelled"], default: "placed" },
placedAt: { type: Date, default: Date.now }
});
orderSchema.index({ "customer._id": 1, placedAt: -1 }); // order history
orderSchema.index({ status: 1, placedAt: -1 }); // fulfilment queues and reportsThe sample code at the top is the heart of the project. Inside withTransaction, each line item's stock is reserved with a conditional $inc whose filter includes stock: { $gte: qty }: the decrement only matches while enough stock exists, and it is atomic per document. If any line fails, the thrown error aborts the transaction and every earlier decrement is rolled back. Order.create([...], { session }) uses the array form because that overload accepts a session, and the 409 reaches the client through the error middleware.
// GET /products?q=lamp&after=<lastId>
router.get("/", async (req, res, next) => {
try {
const filter = {};
if (req.query.q) filter.$text = { $search: req.query.q };
if (req.query.after) filter._id = { $gt: new mongoose.Types.ObjectId(req.query.after) };
const products = await Product.find(filter).sort({ _id: 1 }).limit(20).lean();
res.json({ products, next: products.at(-1)?._id ?? null });
} catch (err) { next(err); }
});Order history is Order.find({ "customer._id": id }).sort({ placedAt: -1 }).limit(20).lean(), served by the compound index; confirm with explain("executionStats").
The daily report is an aggregation that a scheduled job materialises with $merge, so the endpoint reads a tiny collection:
await Order.aggregate([
{ $match: { status: { $in: ["paid", "shipped"] }, placedAt: { $gte: from, $lt: to } } },
{ $group: { _id: { $dateTrunc: { date: "$placedAt", unit: "day" } },
revenue: { $sum: "$total" }, orders: { $sum: 1 } } },
{ $merge: { into: "dailysales", on: "_id", whenMatched: "replace", whenNotMatched: "insert" } }
]);Low-stock alerts come from a change stream filtered to updates that push stock below the threshold:
const stream = Product.watch(
[{ $match: { operationType: "update", "updateDescription.updatedFields.stock": { $lt: 5 } } }],
{ fullDocument: "updateLookup" }
);
for await (const change of stream) {
await notifyPurchasing(change.fullDocument.sku, change.fullDocument.stock);
}| Requirement | Technique from the course |
|---|---|
| No overselling under concurrent orders | conditional $inc inside a transaction |
| Fast history and reports | compound indexes in ESR order, $merge materialized view |
| Restorable data, least privilege | scheduled mongodump --oplog; an orders_api user with readWrite only |
Seed the database, place overlapping orders from two terminals, confirm stock never goes negative, then rehearse a backup and restore.
Why does the order route decrement stock with `{ _id, stock: { $gte: qty } }` rather than checking stock first and then updating?
{ "customer._id": 1, placedAt: -1 } for order history.$merge and react to threshold events with filtered change streams.Next lesson: What to learn next — deepen your skills with MongoDB University's Associate Developer path, Atlas Search and Vector Search, and the performance and operations material for running MongoDB at scale.