Adding Cart Line Items

Adding Cart Line Items

Welcome back! 👋 Now that we can create carts and fetch them in a stable, predictable shape, it’s time to make a cart useful by adding items to it.

In this lesson you’ll follow the full “Add to cart” flow end-to-end: the API route parses and validates the request, the service layer enforces business rules (like inventory limits), and the repository performs an add-or-increment write safely inside a transaction. This is the exact behavior you see in real e-commerce backends when a user clicks “Add to cart” multiple times.

Previously…

In the last lesson, we treated the cart as a first-class backend resource: you created carts and fetched them back fully “hydrated” with items and computed totals. That stable response shape is what makes today’s work clean—once we add cart_items rows, GET /api/carts/:id automatically becomes meaningful because totals can now be computed from those line items.

What a “line item” represents

A cart line item is one row in cart_items. It captures:

  • Which product was added (product_id)
  • How many (quantity)
  • What price was used when it was added (unit_price_cents)

That last field is a snapshot: it preserves stability if product prices change later. Our totals logic (from the previous lesson) uses this snapshot when computing subtotal and tax.

The API endpoint for adding items

The main entry point for “Add to cart” is:

  • POST /api/carts/:id/items

This is implemented in src/app/api/carts/[id]/items/route.ts. The handler is deliberately strict: it validates the cart ID, safely parses JSON, validates the body shape, then delegates to the service layer and converts the result into a consistent HTTP response.

Route setup and imports:

This first part shows what the route depends on and how Next.js provides dynamic route params in this codebase.

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

type RouteContext = { params: Promise<{ id: string }> };
  • success and error are the project’s response helpers, so every route returns a consistent envelope instead of hand-rolling NextResponse formatting in each file.
  • parseJson(req) is used instead of req.json() directly so the route can handle malformed JSON without crashing. That keeps error handling consistent and prevents “unexpected 500s” for simple client mistakes.
  • RouteContext is important: in this project, context.params is a Promise. That’s why the handler does const { id } = await context.params instead of accessing params synchronously.
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