Shop Microservers
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
| Route | Service | Port | Storage | Responsibility |
|---|---|---|---|---|
/api/auth/ | auth | 3004 | auth_db (PG) | Registration, login, JWT issuance |
/api/catalog/ | catalog | 3001 | catalog_db(PG) | Product listing and stock management |
/api/cart/ | cart | 3002 | redis | Ephemeral per-user cart (7-day TTL) |
/api/orders/ | orders | 3003 | orders_db(PG) | Checkout: validate stock → decrement → persist order → clear cart |
/ | frontend | 3000 | — | Next.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:
- Reads the user's cart by calling cart (
fetchCart). - Decrements stock item by item by calling catalog (
decrementStock). - Persists the order with its items into
orders_db. - 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 forwardsUpgrade/Connectionheaders 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
| Service | Method & route (internal) | Description |
|---|---|---|
| auth | POST /register | Create user and return JWT |
| auth | POST /login | Authenticate and return JWT |
| catalog | GET / | List products |
| catalog | GET /:id | Product detail |
| catalog | PATCH /:id/stock | Adjust stock (internal use) |
| cart | GET / | Get the user's cart |
| cart | POST /items | Add an item |
| cart | DELETE /items/:productId | Remove an item |
| cart | DELETE / | Empty the cart |
| orders | GET / | List the user's orders |
| orders | GET /:id | Order detail |
| orders | POST / | Checkout (create the order) |
Data Model
- auth_db →
User(id, email, passwordHash, createdAt). - catalog_db →
Product(id, name, price, imageUrl, stock, category). - orders_db →
Order(statusPENDING → CONFIRMED → SHIPPED → DELIVERED / CANCELLED) andOrderItem. - redis → per-user carts, no persistence.
How to Run
The app becomes available at http://localhost (or http://localhost:${GATEWAY_PORT}).
Configuration
| Variable | Default | Required | Purpose |
|---|---|---|---|
JWT_SECRET | — | Yes | Signs/verifies JWTs (7-day expiry) |
GATEWAY_PORT | 80 | No | Host port for the Nginx gateway (use 8080 if 80 is taken) |
NEXT_PUBLIC_API_URL | http://localhost | No | Frontend API base URL, embedded at build time |
POSTGRES_USER / POSTGRES_PASSWORD | shop / shop | No | Shared credentials across the three Postgres instances — change before production |
AUTH_DB_NAME / CATALOG_DB_NAME / ORDERS_DB_NAME | auth / catalog / orders | No | Per-service database names |
REDIS_URL | redis://redis:6379 | No | Cart service's Redis connection |
CATALOG_URL / CART_URL | http://catalog:3001 / http://cart:3002 | No | Internal service URLs the orders service calls during checkout |
Each microservice also receives an auto-generated
DATABASE_URLfrom 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_dbnever blocksauthorordersfrom 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.