Shop Microservers

DockerNext.jsExpressPostgreSQLRedisNginx

Shop Microservers is a full-stack e-commerce application designed to demonstrate a real-world, decoupled microservices architecture. Each business domain (auth, catalog, cart, orders) lives in its own service, with its own database and its own deployment lifecycle, while a single gateway exposes a cohesive HTTP interface to the frontend.

Architecture and Tech Stack

Core Architecture

  • Pattern: Microservices with database-per-service (each service owns its data).
  • Gateway: Nginx as a reverse proxy and single entry point (port 80).
  • Communication: Internal HTTP over a Docker bridge network (shop), using service names as hostnames.
  • Orchestration: Docker Compose with health checks and health-based dependencies (condition: service_healthy).
  • API contract: Uniform { success, data, error } response across all services.

Layered Diagram

Services and Responsibilities

RouteServicePortStorageResponsibility
/api/auth/auth3004auth_db (PG)Registration, login, JWT issuance
/api/catalog/catalog3001catalog_db(PG)Product listing and stock management
/api/cart/cart3002redisEphemeral per-user cart (7-day TTL)
/api/orders/orders3003orders_db(PG)Checkout: validate stock → decrement → persist order → clear cart
/frontend3000Next.js App Router (SPA, JWT in localStorage)

8 containers in total: gateway, frontend, 4 microservices, 3 PostgreSQL, and 1 Redis.

Tech Stack

  • Backend: Node.js + TypeScript, Express, Prisma ORM, Zod (validation).
  • Frontend: Next.js 15 (App Router), React 19, Tailwind CSS 3, Radix UI, lucide-react.
  • Databases: PostgreSQL 16 (auth, catalog, orders — one per service) and Redis 7 (cart).
  • Gateway: Nginx (reverse proxy).
  • Orchestration: Docker Compose.

Request Flow

Tools/Features at a Glance

Key Features

True database-per-service

Every stateful service has its own PostgreSQL instance (auth_db, catalog_db, orders_db), removing shared-database coupling. The schema is applied via Prisma when each container starts.

Orchestrated checkout flow

The orders service orchestrates the purchase transaction:

  1. Reads the user's cart by calling cart (fetchCart).
  2. Decrements stock item by item by calling catalog (decrementStock).
  3. Persists the order with its items into orders_db.
  4. Clears the cart by calling cart (clearCart).

Ephemeral Redis cart

The cart lives in Redis under the key cart:{userId} with a 7-day TTL and no disk persistence: lightweight, fast, and disposable by design.

Shared-secret JWT auth

The JWT_SECRET is shared via environment variable across auth, cart, and orders, so any service can verify tokens without calling back to auth. Tokens expire after 7 days; there's no introspection endpoint, so each service validates the signature locally.

Technical Highlights

  • Gateway as the only surface: The frontend never calls services directly — everything goes through Nginx, routed by prefix (/api/<service>/).
  • Cascading health checks: The gateway depends on all 4 microservices being healthy (15s check interval, 20s for gateway/frontend, 5 retries); each service also waits for its own database to be healthy, and orders additionally waits on cart and catalog before it starts.
  • Prefix stripping + WebSocket passthrough: Nginx strips the /api/<service>/ prefix before proxying to the upstream, and forwards Upgrade/Connection headers so Next.js HMR still works through the gateway in development.
  • Auto-seeded catalog: Seeds 12 products automatically on first boot (idempotent).
  • Validation as a boundary: Zod validates input in each service before touching the database.
  • Uniform error handling: AppError + error middleware → consistent { success, data, error } responses.

Main Endpoints

ServiceMethod & route (internal)Description
authPOST /registerCreate user and return JWT
authPOST /loginAuthenticate and return JWT
catalogGET /List products
catalogGET /:idProduct detail
catalogPATCH /:id/stockAdjust stock (internal use)
cartGET /Get the user's cart
cartPOST /itemsAdd an item
cartDELETE /items/:productIdRemove an item
cartDELETE /Empty the cart
ordersGET /List the user's orders
ordersGET /:idOrder detail
ordersPOST /Checkout (create the order)

Data Model

  • auth_dbUser (id, email, passwordHash, createdAt).
  • catalog_dbProduct (id, name, price, imageUrl, stock, category).
  • orders_dbOrder (status PENDING → CONFIRMED → SHIPPED → DELIVERED / CANCELLED) and OrderItem.
  • redis → per-user carts, no persistence.

How to Run

cp .env.example .env
docker compose up --build

The app becomes available at http://localhost (or http://localhost:${GATEWAY_PORT}).

Configuration

VariableDefaultRequiredPurpose
JWT_SECRETYesSigns/verifies JWTs (7-day expiry)
GATEWAY_PORT80NoHost port for the Nginx gateway (use 8080 if 80 is taken)
NEXT_PUBLIC_API_URLhttp://localhostNoFrontend API base URL, embedded at build time
POSTGRES_USER / POSTGRES_PASSWORDshop / shopNoShared credentials across the three Postgres instances — change before production
AUTH_DB_NAME / CATALOG_DB_NAME / ORDERS_DB_NAMEauth / catalog / ordersNoPer-service database names
REDIS_URLredis://redis:6379NoCart service's Redis connection
CATALOG_URL / CART_URLhttp://catalog:3001 / http://cart:3002NoInternal service URLs the orders service calls during checkout

Each microservice also receives an auto-generated DATABASE_URL from Docker Compose interpolation — no manual wiring needed.

Project Structure

shopflow-microservices/
├── docker-compose.yml        # Orchestrates the 8 containers
├── gateway/                  # Nginx (reverse proxy)
├── frontend/                 # Next.js 15 (login, catalog, cart, orders)
└── services/
    ├── auth/                 # Express + Prisma → auth_db
    ├── catalog/              # Express + Prisma → catalog_db
    ├── cart/                 # Express + ioredis → redis
    └── orders/               # Express + Prisma → orders_db (checkout orchestrator)

Impact and Scalability

  • Independent scaling: each microservice (auth, catalog, cart, orders) can be scaled, redeployed, or rolled back on its own, without touching the others.
  • Isolated failure domains: database-per-service means a slow query or outage in catalog_db never blocks auth or orders from serving traffic.
  • Stateless services behind the gateway: every service can run multiple replicas behind Nginx with no sticky-session requirement, since state lives in Postgres/Redis, not in-process.
  • Clear team boundaries: the uniform { success, data, error } contract and per-service Prisma schemas make it straightforward for different owners to evolve each service independently.

Notes

This report reflects the current architecture with independent databases per service (auth, catalog, and orders), internal HTTP communication over Docker, and an Nginx gateway as the single entry point. The design emphasizes decoupling, health checks, and a uniform API contract across all services. Code is public on GitHub.


© 2026 Felipe Giraldo