Managing Order Transitions
Managing Order Transitions
Huge congrats for making it this far. 🎉 You’ve built a full “mini e-commerce backend” flow: carts → items → checkout → orders → retrieval. What’s left now is the part that makes orders feel “alive”: state transitions.
In this final lesson, you’ll implement the two most important “actions” on an order in this codebase:
- Pay an order:
POST /api/orders/:id/pay - Cancel an order:
POST /api/orders/:id/cancel
You’ll see the same layering pattern you’ve used throughout the course:
- Routes validate input and enforce HTTP method rules.
- Services enforce business rules like “only pending orders can be paid.”
- Repositories persist state changes with a safe
UPDATE ... RETURNING ....
What “Order Transitions” Mean Here
Orders have a status field with a small set of allowed values:
pending(created at checkout)paidshippedcancelled
This project intentionally uses action routes (/pay, /cancel) instead of a generic “update status” endpoint. That’s because transitions are not “free-form edits”—they’re business actions with rules:
- Paying is only valid from
pending. - Canceling is blocked if the order is already
shippedor alreadycancelled.
Those rules belong in the service layer, so every caller follows the same policy.
Repository: Persisting a Status Change With setOrderStatus
Learners will implement setOrderStatus(id, status) in src/lib/repositories/ordersRepo.ts.
The requirements are very specific:
-
Run an update:
UPDATE orders SET status = $1, updated_at = now() WHERE id = $2 RETURNING *
-
If no row is returned, return
null(so the service can turn it into a404) -
If a row is returned, map it with
mapOrderRow(...)so the rest of the app sees domain data
Here’s what that looks like in this repo style:
RETURNING *is doing a lot of work for you: you don’t need to perform a second query to re-fetch the updated order, which keeps transitions fast and consistent.- The
rows[0] ? ... : nullshape is a deliberate contract: repositories don’t decide HTTP status codes; they simply report “updated” vs “not found.” - Mapping via
mapOrderRowkeeps the “no raw DB rows beyond the repo” rule intact. Services/routes never deal withOrderRowdirectly.
