Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/astrxnomo/shop-microservers/llms.txt

Use this file to discover all available pages before exploring further.

The Orders service orchestrates the checkout process for Shop Microservers. When a user places an order, this service acts as the transaction coordinator: it fetches the user’s cart, decrements stock for each item in the catalog, persists a new order record in PostgreSQL, and clears the cart — all in sequence. It also provides endpoints to retrieve order history and individual order details, each scoped to the authenticated user. Because it depends on both the cart and catalog services, it communicates with them over Docker’s internal network rather than going through the public gateway.

Overview

Runtime

Express on port 3003 inside Docker. Gateway prefix: /api/orders/.

Database

PostgreSQL — database name orders_db. Schema managed by Prisma.

Authentication

All endpoints require a valid JWT Bearer token. Orders are strictly user-scoped.

Internal Clients

Calls catalog at http://catalog:3001 and cart at http://cart:3002 over Docker’s internal network.

Endpoints

MethodPathAuthDescription
GET/RequiredList all orders for the authenticated user
GET/:idRequiredGet a single order by ID
POST/RequiredCheckout: convert cart into an order
All Orders endpoints are reachable through the gateway at /api/orders/. The JWT must be included in the Authorization: Bearer <token> header on every request.

Checkout Flow

Placing an order (POST /) triggers a multi-step sequence that spans three services:
1

Fetch the cart

The orders service calls GET / on the cart service, forwarding the user’s JWT so the cart service can identify the user.
const cart = await cartClient.fetchCart(token);
2

Validate cart is not empty

If the cart contains no items, a 400 Bad Request is returned immediately and no stock is modified.
if (!cart.items.length) throw new AppError("Cart is empty", 400);
3

Decrement stock for each item

For every item in the cart, the service calls PATCH /products/:id/stock on the catalog service with { decrement: quantity }. If any product has insufficient stock, the catalog returns 409 and the checkout fails.
for (const item of cart.items) {
  await catalogClient.decrementStock(item.productId, item.quantity);
}
4

Persist the order

A new Order record is created in orders_db with all line items embedded as OrderItem records and the total carried over from the cart.
const order = await prisma.order.create({
  data: {
    userId,
    total: cart.total,
    items: {
      create: cart.items.map((i) => ({
        productId: i.productId,
        name: i.name,
        price: i.price,
        quantity: i.quantity,
      })),
    },
  },
  include: { items: true },
});
5

Clear the cart

After the order is saved, the service calls DELETE / on the cart service to empty the user’s Redis cart.
await cartClient.clearCart(token);
6

Return the order

The newly created order — including all embedded OrderItem records — is returned in the response.

Internal Service Clients

The orders service communicates with the catalog and cart services using lightweight fetch-based clients: Cart client (src/clients/cart.client.ts):
export async function fetchCart(token: string): Promise<CartData> {
  const res = await fetch(`${config.cartUrl}/`, {
    method: "GET",
    headers: { Authorization: `Bearer ${token}` },
  });
  if (!res.ok) throw new AppError("Failed to fetch cart", 502);
  const payload = await res.json();
  if (!payload.success || !payload.data)
    throw new AppError(payload.error ?? "Invalid cart response", 502);
  return payload.data;
}
Catalog client (src/clients/catalog.client.ts):
export async function decrementStock(
  productId: string,
  quantity: number,
): Promise<void> {
  const res = await fetch(
    `${config.catalogUrl}/products/${productId}/stock`,
    {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ decrement: quantity }),
    },
  );
  if (!res.ok)
    throw new AppError(`Failed to decrement stock for ${productId}`, 502);
}
Client VariableDocker Internal Address
config.cartUrlhttp://cart:3002
config.catalogUrlhttp://catalog:3001

Data Model

model Order {
  id        String      @id @default(cuid())
  userId    String
  items     OrderItem[]
  total     Float
  status    OrderStatus @default(PENDING)
  createdAt DateTime    @default(now())
  updatedAt DateTime    @updatedAt
}

model OrderItem {
  id        String @id @default(cuid())
  orderId   String
  order     Order  @relation(fields: [orderId], references: [id])
  productId String
  name      String
  price     Float
  quantity  Int
}

enum OrderStatus {
  PENDING
  CONFIRMED
  SHIPPED
  DELIVERED
  CANCELLED
}
New orders are always created with status PENDING. Status transitions are not managed by the API in the current implementation — they can be updated directly in the database or via a future admin service.

User Scoping

All order queries include a userId filter derived from the JWT sub claim. This means a user can never read or interact with another user’s orders:
export async function getOrder(id: string, userId: string) {
  const order = await prisma.order.findFirst({
    where: { id, userId },
    include: { items: true },
  });
  if (!order) throw new AppError("Order not found", 404);
  return order;
}

Response Shape

{
  "success": true,
  "data": {
    "id": "clxyz789",
    "userId": "clxyz123",
    "total": 179.98,
    "status": "PENDING",
    "createdAt": "2024-06-01T12:00:00.000Z",
    "updatedAt": "2024-06-01T12:00:00.000Z",
    "items": [
      {
        "id": "clitem001",
        "orderId": "clxyz789",
        "productId": "clprod456",
        "name": "Auriculares Inalámbricos Pro",
        "price": 89.99,
        "quantity": 2
      }
    ]
  }
}
For GET /, the data field is an array of Order objects, ordered by createdAt descending.

Error Cases

HTTP StatusCondition
400Cart is empty at checkout time
401Missing or invalid JWT
404Order ID not found, or order does not belong to the authenticated user
409A product in the cart has insufficient stock (proxied from catalog)
502Upstream call to cart or catalog service failed

API Reference

GET /

List all orders for the current user.

GET /:id

Get a single order by ID.

POST /

Checkout and place a new order.

Build docs developers (and LLMs) love