PROJECT CASE STUDY

ShopNest

A commerce system engineered for correctness under concurrency — atomic inventory, idempotent checkout, refresh-token theft detection, and order history that survives catalogue changes.

ShopNest commerce interface showing product discovery, product details, cart state, pricing, and checkout controls.
RoleBackend architecture, data modelling, and full-stack implementation
Period2025—26
TechnologiesNestJS 11 · Next.js 15 · TypeScript · Prisma · PostgreSQL 17

When two customers buy the last unit

A storefront is easy until two customers try to buy the final unit at the same moment. Commerce correctness lives in races around inventory, duplicate checkout requests, authentication sessions, and preserving historical order data after products change.

Preventing overselling and deadlocks

THE RISK

Two concurrent checkouts can both observe the final unit as available before either decrements it. Row locking fixes that race, but locking overlapping carts in inconsistent order introduces deadlocks.

THE APPROACH

The cart acts as the serialization point, product rows are acquired in deterministic order, and stock is decremented with a conditional update only when enough quantity remains. A zero-row update means another transaction won, so the request fails safely instead of allowing negative inventory.

Transactions as the correctness boundary

The project uses a Turborepo monorepo with a NestJS API and Next.js frontend sharing OpenAPI-generated TypeScript contracts. Money is represented as integer cents rather than floating point. Order items snapshot purchase-time data so later catalogue changes cannot rewrite history, while checkout correctness is enforced inside database transactions rather than through application-level reads.

NestJS 11Next.js 15TypeScriptPrismaPostgreSQL 17TurborepoArgon2idGitHub Actions

Checkout behavior under concurrent requests

Concurrency-sensitive behaviour is covered by automated tests, and important architectural decisions are documented explicitly instead of being left implicit in implementation details.

Separating transaction outcomes from errors

One authentication path originally revoked a compromised token family and then threw inside the same transaction, causing the revocation to roll back. The fix reinforced an important boundary: transactions should return a decision; control flow should happen after the transaction commits.