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 the carts table)
  • itemId: the cart item UUID (from the cart_items table)

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:

  • PATCH for updating quantity
  • DELETE for 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.
  • RouteContext again defines params as a Promise, so we always await context.params to read id and itemId.
  • parseJson is used for PATCH so malformed JSON becomes a clean , rather than a thrown exception that would force a generic .
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 id and itemId are 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.
  • validateUpdateItem ensures 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.httpStatus and 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_items row, validating both id and itemId keeps the endpoint consistent and prevents confusing “half-valid” requests.
  • deleteItemService returns a ServiceResult<{ 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_FOUND becomes 404, cart state conflicts become 409, 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.

  • quantity must 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 means updateItemService receives a properly shaped UpdateCartItemInput every 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 performs the actual write. The extra 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 open cart 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 404 and 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

The repository is where SQL happens. These functions are intentionally small: they do one query, then return a domain-friendly result.

This is implemented in src/lib/repositories/cartsRepo.ts and updates by both cart_id and id.

  • The WHERE cart_id = $2 AND id = $3 condition is important: it ensures we only update the item if it belongs to the given cart. That matches how the route is structured and prevents cross-cart updates.
  • updated_at = now() makes edits observable. In real systems this is helpful for debugging, analytics, and “last modified” UI behavior.
  • RETURNING * avoids a second query. If the update succeeds, we immediately get the updated row back to send to the client.
  • Returning null is a clean signal: “no matching row existed,” which the service then turns into a 404 with a consistent error envelope.
Deleting a cart item

This delete function returns a boolean so the service can easily map “not found” vs “deleted.”

  • Like the update, this delete is scoped to both cart_id and id. That prevents deleting an item that belongs to another cart.
  • DELETE ... RETURNING * is a practical pattern: if a row was deleted, Postgres returns it; if not, the result is empty. That makes it easy to compute a boolean success value.
  • Returning Boolean(rows[0]) gives higher layers a simple “did we delete something?” signal without exposing database details.
Recap

In this lesson you completed the cart editing experience:

  • src/app/api/carts/[id]/items/[itemId]/route.ts exposes:

    • PATCH to update quantity (validates IDs + JSON + payload)
    • DELETE to remove the item (validates IDs, no body)
  • validateUpdateItem enforces a strict update contract: quantity must be an integer ≥ 1.

  • updateItemService and deleteItemService enforce business rules consistently:

    • cart must exist and be open
    • item must exist within that cart
    • updates must respect product availability and inventory
  • updateCartItemQuantity and deleteCartItem perform 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.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal