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 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:

// src/app/api/carts/[id]/items/[itemId]/route.ts
import { NextRequest } from 'next/server';
import { success, error, parseJson } from '@/lib/http/response';
import { isUUID } from '@/lib/http/validation';
import { updateItemService, deleteItemService, validateUpdateItem } from '@/lib/services/cartsService';

type RouteContext = { params: Promise<{ id: string; itemId: string }> };
  • 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 400, rather than a thrown exception that would force a generic 500.

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:

// src/app/api/carts/[id]/items/[itemId]/route.ts
export async function PATCH(req: NextRequest, context: RouteContext) {
  try {
    const { id, itemId } = await context.params;

    if (!isUUID(id) || !isUUID(itemId)) return error('VALIDATION_ERROR', 'Invalid id or itemId', undefined, 400);

    const bodyResult = await parseJson(req);
    if (!bodyResult.ok) return error(bodyResult.code, bodyResult.message, undefined, 400);

    const valid = validateUpdateItem(bodyResult.value);
    if (!valid.ok) return error('VALIDATION_ERROR', valid.message, undefined, 400);

    // ...continued below
  • 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.
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