Managing Shopping Carts

Creating and Viewing Carts

Welcome! 👋 In this lesson, you’ll add the first two “entry points” for the cart system: creating a cart and fetching a cart by ID. These endpoints are the foundation for everything that comes next—adding items, updating quantities, and eventually checkout.

You’ll see how this codebase treats a cart as a real backend resource with its own identity, status, and timestamps. You’ll also learn how our cart reads are hydrated: when you fetch a cart, the repository returns the cart plus items and computed totals so client code can render the cart without doing extra math or special-casing.

Previously…

In the previous lesson, you implemented product lifecycle endpoints like PATCH and DELETE (archive) for /api/products/:id. You validated URL params up front, delegated the “meaning” of the operation to the service layer, and returned consistent response envelopes using success(...) and error(...).

We’ll reuse that same pattern here for carts: validate the incoming id, call a service, and let the service decide whether the outcome is NOT_FOUND, CONFLICT, or a successful cart object.

Carts are first-class resources

A cart is not just “a list of products.” In this project, a cart has:

  • A stable id (UUID), so it can exist even before it has items.
  • A status lifecycle: "open", "checked_out", "abandoned".
  • Timestamps (created_at, updated_at) managed by the database.
  • A consistent read shape that includes items and totals, so clients don’t need special “empty cart” branching.

That consistent read shape is built in the repository at src/lib/repositories/cartsRepo.ts, which composes the cart from multiple queries and computes money totals in cents.

Repository essentials: mapping rows into safe domain values

Before we insert or read carts, the repository defines helpers that translate raw database values into domain-safe ones.

This helper lives in src/lib/repositories/cartsRepo.ts. It ensures that whatever string comes back from the database is converted into one of our known cart statuses.

// src/lib/repositories/cartsRepo.ts
function mapCartStatus(status: string): Cart["status"] {
  return ["open", "checked_out", "abandoned"].includes(status)
    ? (status as Cart["status"])
    : "open";
}
  • The database column status is a string, but the rest of the app expects a limited set of meaningful states. This function is the “boundary guard” that prevents unexpected strings from leaking into the domain layer.
  • Falling back to "open" is a safe default: it keeps the cart usable instead of crashing a request because of bad data. It also makes your API behavior more resilient if the database ever contains legacy or malformed values.
  • Doing this mapping in the repository keeps the rest of the codebase simpler. Every service and route can assume cart.status is one of the allowed statuses.
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