Managing Cart Line Items
Managing Cart Line Items
Welcome back! 👋 In the last lesson you made carts useful by adding line items with a real-world add-or-increment behavior. The route validated inputs, the service enforced business rules like inventory checks, and the repository performed safe writes in a transaction.
In this final lesson, we make the cart feel editable like a real shopping experience. You’ll learn how our API targets a specific cart item row so clients can update quantity or remove an item—without resending product details.
Editing a specific cart item
To update or delete a line item, we address it directly by its cart item ID:
/api/carts/:id/items/:itemId
That means we always deal with two identifiers:
id: the cart UUID (from thecartstable)itemId: the cart item UUID (from thecart_itemstable)
This is important because changing quantity or deleting an item is an operation on the line item row, not on the product itself.
Route: updating and deleting items by ID
The file src/app/api/carts/[id]/items/[itemId]/route.ts contains both endpoints:
PATCHfor updating quantityDELETEfor removing the item
The route follows the same “fail fast” pattern you’ve seen already: validate path params, validate body (for PATCH), call the service, then translate the ServiceResult into an HTTP response.
Route setup and parameter handling:
- This route uses the same response helpers as the rest of the API (
success,error) so responses stay consistent across endpoints. RouteContextagain definesparamsas a Promise, so we alwaysawait context.paramsto readidanditemId.parseJsonis used for PATCH so malformed JSON becomes a clean400, rather than a thrown exception that would force a generic500.
Updating quantity with PATCH
Updating a line item is a “quantity edit.” The client sends { "quantity": number }, and we validate everything before touching the database.
Validating both IDs and parsing the request body:
- Both
idanditemIdare validated up front so invalid URLs are treated as client errors (400) instead of wasting database work. parseJson(req)protects the route from invalid JSON. If the body can’t be parsed, we return a standardized error envelope with an HTTP 400.validateUpdateItemensures the service only receives a clean DTO{ quantity: number }. That way, the service can focus on business rules instead of re-checking types.
Calling the service and returning the updated item
- The route delegates the update to
updateItemService, passing both identifiers plus the validated payload. - If the service returns a failure, the route doesn’t reinterpret it. It uses
updated.error.httpStatusand returns exactly the error code/message/details the service produced. - On success, the route returns the updated
CartItem. This makes the UI experience nicer because the client can immediately reflect the new quantity from the response.
Deleting an item with DELETE
Deleting doesn’t need a request body. We still validate both IDs, then ask the service to delete that specific item.
Validating IDs and calling delete service:
- Even though the delete operation ultimately targets a
cart_itemsrow, validating bothidanditemIdkeeps the endpoint consistent and prevents confusing “half-valid” requests. deleteItemServicereturns aServiceResult<{ deleted: true }>so the route can respond with a clear confirmation payload.- Like the PATCH handler, the DELETE handler treats the service as the source of truth for HTTP mapping:
NOT_FOUNDbecomes404, cart state conflicts become409, etc., without the route needing special cases.
Validation: ensuring updates are meaningful
Update validation is intentionally strict: in this API, “changing quantity” always means setting it to a positive integer. If someone wants to remove an item, they use DELETE instead.
Validating the update payload:
This lives in src/lib/services/cartsService.ts and is called by the PATCH route before the service runs.
quantitymust be an integer and must be at least 1. This avoids “0 quantity” rows, negative quantities, and fractional quantities that would make totals unpredictable.- Returning a cleaned
{ quantity }object meansupdateItemServicereceives a properly shapedUpdateCartItemInputevery time, which keeps business logic simpler and safer. - This validation also creates a clean separation of intent: PATCH means edit quantity, and DELETE means remove the item.
Service: updating an item with business rules
updateItemService coordinates the update process and enforces important rules:
- the cart must exist and be
open - the item must exist
- the associated product must still be available (not archived)
- the new quantity must not exceed inventory
Updating quantity safely:
- The service starts by loading the cart and enforcing lifecycle rules through
ensureCartOpen. This prevents edits on carts that are already checked out or abandoned. - It confirms the item exists inside that cart using
getCartItemByCartAndId(cartId, itemId). That keeps the API honest: you can’t update an item that belongs to a different cart. - The product lookup is used for inventory validation and availability rules. If the product is missing or archived, updating the cart item is treated as a conflict (
409) because the item can’t be meaningfully maintained. - The final call to
updateCartItemQuantity(cartId, itemId, input.quantity)performs the actual write. The extraif (!updated)check handles the edge case where the row disappears between read and update (rare, but possible in concurrent systems).
Service: deleting an item with consistent behavior
Deleting an item is similar: we still enforce “cart must be open,” and we return clean 404s if the item doesn’t exist.
- We again enforce that only an
opencart can be edited. This keeps cart state transitions predictable and avoids “mutating history” after checkout. - We check existence before deleting so we can return a clear, user-friendly
404and message when the item isn’t present. - The delete function returns a boolean, so the service can distinguish “deleted successfully” from “nothing was deleted” and map that to a
404.
Repository: writing the update and delete operations
Deleting a cart item
Recap
In this lesson you completed the cart editing experience:
-
src/app/api/carts/[id]/items/[itemId]/route.tsexposes:PATCHto update quantity (validates IDs + JSON + payload)DELETEto remove the item (validates IDs, no body)
-
validateUpdateItemenforces a strict update contract: quantity must be an integer ≥ 1. -
updateItemServiceanddeleteItemServiceenforce business rules consistently:- cart must exist and be
open - item must exist within that cart
- updates must respect product availability and inventory
- cart must exist and be
-
updateCartItemQuantityanddeleteCartItemperform the precise SQL operations scoped by both cart ID and item ID.
With create, add, update, and delete in place, your cart API now supports the full “editable cart” workflow that real checkout systems depend on.
