Listing and Fetching Orders

Listing and Fetching Orders

Now that checkout can turn an open cart into a pending order, we need a way to actually see what was created. This lesson focuses on the two most common “read” operations in any backend: listing many resources (a collection) and fetching one resource (a single record).

You’ll implement and understand the two Orders endpoints our project exposes: GET /api/orders for paginated lists and GET /api/orders/:id for order details. Along the way, you’ll see how query params and route params are validated at the route layer, while the service layer handles defaults, caps, and “not found” behavior consistently.

Previously: From Cart to Order

In the previous lesson, we implemented checkout as a transactional workflow that snapshots cart items into order items, creates an order with status pending, and marks the cart as checked_out so it can’t be edited anymore. That gave us reliable order records in the database—now we’re building the read side so clients can browse and inspect those records.

Orders Retrieval: Collections vs. Single Resources

There are two ways clients typically access orders:

  • Listing orders (collection): show a page of results, usually newest first.
  • Fetching one order (resource): show details for a specific order, including its items.

In this project, those map to two Next.js route handlers:

  • src/app/api/orders/route.tsGET /api/orders
  • src/app/api/orders/[id]/route.tsGET /api/orders/:id

Both routes follow the same overall pattern: validate inputs early, call a service function, and return a consistent API envelope using success(...) and error(...).

Listing Orders with Pagination

The list endpoint must be safe and scalable. Even a small shop can quickly accumulate thousands of orders, so we never want to return “all orders” in one response. Instead, we paginate using page and pageSize query parameters.

Route: parse and validate query parameters

This code lives in src/app/api/orders/route.ts. Its job is to read query params from the URL, validate them, then delegate to the service layer.

// src/app/api/orders/route.ts
import { NextRequest } from 'next/server';
import { success, error } from '@/lib/http/response';
import { listOrdersService } from '@/lib/services/ordersService';
import { parseOptionalIntParam } from '@/lib/http/validation';

export async function GET(req: NextRequest) {
  try {
    const searchParams = req.nextUrl.searchParams;
    const page = parseOptionalIntParam(searchParams, 'page', { min: 1 });
    if (!page.ok) return error('VALIDATION_ERROR', page.message, undefined, 400);
    const pageSize = parseOptionalIntParam(searchParams, 'pageSize', { min: 1, max: 100 });
    if (!pageSize.ok) return error('VALIDATION_ERROR', pageSize.message, undefined, 400);
    const orders = await listOrdersService({ page: page.value, pageSize: pageSize.value });
    if (!orders.ok) return error(orders.error.code, orders.error.message, orders.error.details, orders.error.httpStatus);
    return success(orders.value);
  } catch (e: unknown) {
    return error('INTERNAL_ERROR', (e as Error).message, undefined, 500);
  }
}
  • req.nextUrl.searchParams is the Next.js-friendly way to access query parameters in route handlers. It gives us a URLSearchParams object, which is exactly what our validation helper expects.
  • parseOptionalIntParam(...) does the heavy lifting of converting string values into numbers and enforcing bounds. The min: 1 for page prevents invalid pages like 0 or -3, which would make pagination math nonsensical.
  • pageSize is capped with { max: 100 }, which protects the database and API from extremely expensive requests. Even if a client tries ?pageSize=100000, this route will reject it with a 400 instead of attempting the query.
  • After validation, the route passes clean numbers into listOrdersService. The route doesn’t implement pagination rules itself beyond basic validation—those defaults and caps are enforced in the service too, so any caller (not just HTTP) stays safe.
  • The route returns consistent response envelopes using success(...) for OK results and error(...) for failures. That keeps your frontend/client code simple because responses are always shaped the same way.
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