Careeroza
Relations and Transactions
Nested Writes
Create an order with its items in one call. Prisma runs it in a transaction automatically.
const order = await this.prisma.order.create({
data: {
user: { connect: { id: userId } },
total: new Prisma.Decimal('2598.00'),
items: {
create: [
{ product: { connect: { id: 7 } }, quantity: 2, unitPrice: new Prisma.Decimal('1299.00') },
],
},
},
include: { items: { include: { product: { select: { name: true } } } } },
});Interactive Transactions
When later steps depend on earlier reads, use $transaction(async (tx) => ...). Everything inside uses tx; a thrown error rolls back.
src/orders/orders.service.ts
import { Injectable, UnprocessableEntityException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class OrdersService {
constructor(private readonly prisma: PrismaService) {}
place(userId: number, items: { productId: number; quantity: number }[]) {
return this.prisma.$transaction(async (tx) => {
let total = new Prisma.Decimal(0);
const lines: Prisma.OrderItemCreateWithoutOrderInput[] = [];
for (const { productId, quantity } of items) {
const { count } = await tx.product.updateMany({
where: { id: productId, stock: { gte: quantity } },
data: { stock: { decrement: quantity } },
});
if (count === 0) throw new UnprocessableEntityException(`Product ${productId} is out of stock`);
const product = await tx.product.findUniqueOrThrow({ where: { id: productId } });
total = total.add(product.price.mul(quantity));
lines.push({ product: { connect: { id: productId } }, quantity, unitPrice: product.price });
}
return tx.order.create({
data: { userId, total, items: { create: lines } },
include: { items: true },
});
});
}
}Example
Why the where stock >= quantity trick matters: two customers buy the last unit at the same time. A "read, check, then write" version oversells; the atomic conditional update does not.
let stock = 1;
async function naiveBuy(name: string) {
const current = stock; // read
await new Promise((r) => setTimeout(r, 10)); // other request runs here
if (current >= 1) { stock = current - 1; return name + ': bought (stock now ' + stock + ')'; }
return name + ': out of stock';
}
function atomicBuy(name: string) {
// like UPDATE products SET stock = stock - 1 WHERE id = 7 AND stock >= 1
const count = stock >= 1 ? (stock -= 1, 1) : 0;
return name + (count ? ': bought (stock now ' + stock + ')' : ': out of stock');
}
async function main() {
console.log('naive:', await Promise.all([naiveBuy('Asha'), naiveBuy('Ravi')]));
console.log('units sold from 1 in stock: 2 (oversold!)');
stock = 1;
console.log('atomic:', [atomicBuy('Asha'), atomicBuy('Ravi')]);
}
main();Output
naive: [ 'Asha: bought (stock now 0)', 'Ravi: bought (stock now 0)' ] units sold from 1 in stock: 2 (oversold!) atomic: [ 'Asha: bought (stock now 0)', 'Ravi: out of stock' ]