Inventory without overselling

TeselaDB

TeselaDB

TeselaDB team

30 minIntermediate

Take the stock and create the order in a single transaction so two purchases at once never sell the same unit.

When several customers buy the last product at once, each purchase has to take the stock and create its order without stepping on the others. With a multi-document transaction both writes happen together or neither does, whether the stock and the orders are in the same range or in ranges on different nodes.

What you will build

A Node.js placeOrder function that takes units only if there are enough left and creates the order in the same transaction. Five purchases at once of a product with three units leave three orders and the stock at zero.

Requirements

  • Node.js 18 or later with the mongodb package.
  • A TeselaDB cluster and a user with readWrite on the shop database.
  • The connection string in the TESELADB_URI environment variable.

1. Take the stock with a condition

The available: { $gte: quantity } filter makes the update change the document only if there are enough units left. If nothing changes, the function throws an error and the whole transaction is undone.

2. Write the purchase in a transaction

withTransaction retries the transaction when it collides with another one that touched the same document, so you do not write the retries yourself. Save the program as inventory.mjs:

JavaScript
import { MongoClient } from "mongodb";

const client = new MongoClient(process.env.TESELADB_URI);
const db = client.db("shop");

// Takes the units from the stock and writes the order in one transaction: both happen, or neither.
async function placeOrder(sku, quantity, customer) {
  const session = client.startSession();
  try {
    return await session.withTransaction(async () => {
      const taken = await db
        .collection("stock")
        .updateOne({ _id: sku, available: { $gte: quantity } }, { $inc: { available: -quantity } }, { session });
      if (taken.modifiedCount === 0) throw new Error(`not enough stock for ${sku}`);
      await db.collection("orders").insertOne({ sku, quantity, customer, createdAt: new Date() }, { session });
    });
  } finally {
    await session.endSession();
  }
}

await db.collection("stock").insertOne({ _id: "chair-red", available: 3 });
const customers = ["Ana", "Luis", "Marta", "Pablo", "Sara"];
const results = await Promise.allSettled(customers.map((customer) => placeOrder("chair-red", 1, customer)));
const placed = results.filter((result) => result.status === "fulfilled").length;
const { available } = await db.collection("stock").findOne({ _id: "chair-red" });
console.log(`${placed} orders placed, ${available} left`);
await client.close();

Try it

Run node inventory.mjs against an empty database. The five purchases go at once, three find stock and two fail with not enough stock:

3 orders placed, 0 left

Clean up

JavaScript
db.getSiblingDB("shop").stock.deleteOne({ _id: "chair-red" });
db.getSiblingDB("shop").orders.deleteMany({ sku: "chair-red" });

From mongosh, the first command deletes the test product and the second deletes its orders.

Next steps