Transactions
Use withTransaction when you need to execute multiple service operations atomically. It is an instance method called from a controller endpoint — the execution context comes from the request.
The callback receives the session and a transaction-scoped copy of the service as its second argument. Use that instance directly inside the callback. For other services, pass { session } explicitly to for().
@(HttpOperation({ method: 'POST', path: 'transfer-balance' })
.QueryParam('fromId', String)
.QueryParam('toId', String)
.QueryParam('amount', Number))
async transferBalance(ctx: HttpContext) {
const { fromId, toId, amount } = ctx.queryParams;
await this.accountsService.for(ctx).withTransaction(async (session, accountsSvc) => {
await accountsSvc.updateOnly(fromId, {
balance: { $inc: -amount } as any,
});
await accountsSvc.updateOnly(toId, {
balance: { $inc: amount } as any,
});
});
}
Multi-collection transactions
When multiple services are involved, pass session explicitly to each for() call so they all join the same transaction:
async placeOrder(ctx: HttpContext) {
const orderInput = await ctx.request.readBody<OrderInputDTO>();
await this.ordersService.for(ctx).withTransaction(async (session, ordersSvc) => {
const order = await ordersSvc.create(orderInput);
await this.inventoryService.for(ctx, { session }).updateMany(
{ quantity: { $inc: -1 } },
{ filter: { productId: order.productId } },
);
await this.customerService.for(ctx, { session }).updateOnly(order.customerId, {
_$push: { orderIds: order._id },
});
});
}
Transactions inside a service
Expose a dedicated method on the service when you need a transaction that coordinates multiple operations internally. Inside service methods you don't need to pass ctx — this already carries the context set by the caller via for(ctx). Pass this to other services' for() so they inherit the same context:
export class OrdersService extends MongoCollectionService<Order> {
constructor(
db: Db,
private readonly inventory: InventoryService,
) {
super(Order, { db });
}
async createWithInventoryCheck(input: PartialDTO<Order>) {
return this.withTransaction(async (session, self) => {
const order = await self.create(input);
await this.inventory.for(this, { session }).updateOnly(
input.productId,
{ $inc: { quantity: -input.quantity } } as any,
);
return order;
});
}
}
Full API reference
→ MongoService — withTransaction