Node.js + MongoDB
Transactions, Timeouts and Repositories
withSession, maxTimeMS, and keeping MongoDB behind a small data layer.
Module 12 showed startTransaction. In Node, prefer `session.withTransaction(async () => { ... })`: it retries transient errors (failover). Still pass { session } into every operation. Still abort on sold-out — that is not transient. Keep the callback short: no HTTP to Stripe inside the txn.
await client.withSession(async (session) => {
await session.withTransaction(async () => {
await orders.insertOne(doc, { session });
const r = await products.updateOne(
{ name: "Mechanical Keyboard", stock: { $gte: 1 } },
{ $inc: { stock: -1 } },
{ session, maxTimeMS: 2_000 }
);
if (r.matchedCount === 0) throw new Error("sold out");
});
});| Timeout | What it bounds |
|---|---|
| maxTimeMS | This operation on the server |
| serverSelectionTimeoutMS | Finding a usable server (client option) |
| socketTimeoutMS | Idle socket — rarely what you mean |
| Transaction lifetime | ~60s default for the whole txn |
A repository is a module that receives Db or collections, not req. Routes call placeOrder({ userId, sku }). Tests pass a db. You can read maxTimeMS in one place. You cannot hide a per-request MongoClient behind a repository and call it architecture. Mongoose is another wrapper — next module — not a requirement for this layer.
Interview question
How do you run transactions and timeouts in the Node.js driver?
client.withSession and session.withTransaction, passing session into every operation. withTransaction retries transient cluster errors, not business failures. maxTimeMS limits a single operation on the server; serverSelectionTimeoutMS is waiting for a primary. I keep Mongo access behind functions that accept a Db so routes do not own the client.