|
| Spring Boot microservices ecommerce — gateway, domain services, storefront, and admin panel |
If you have ever searched for a Spring Boot microservices ecommerce example, you already know the pattern: two services, one MySQL schema, and a README that says “the rest is homework.”
This post is about a different kind of sample. It is a multivendor marketplace you can clone and run with Docker. Customers shop. Sellers apply and list products. Admins moderate the platform. Each business area is its own Spring Boot service with its own database.
Source code (this is my repository — dofollow): github.com/needyamin/spring-boot-microservices-ecommerce
You do not need to be a distributed-systems expert to follow along. I will explain each idea in plain language first, then show how this project uses it.
What this article covers
- The mall analogy (why not one giant app)
- What you open after Docker starts
- The technology list
- How one click travels through the gateway
- REST vs Kafka, outbox, and saga
- Who owns which data
- Login and roles
- Checkout, webhooks, and order states
- A full “day in the life of an order”
- How a multi-seller cart is split
- The admin BFF
- API examples you can copy into Postman
- Flyway, folders, and how to run it
- Gateway route map
- How I would test a change
- What to build next
- Troubleshooting and FAQ
Think of a mall, not a single shop
A normal online store is one shop: one counter, one cash register, one stockroom. A marketplace is a mall.
In a mall, the clothing store does not open the electronics store’s cash drawer. The food court does not share a ledger with the cinema. There is still a front door, security, and a public directory.
That is the mental model:
- API gateway = the mall entrance and security desk
- Each microservice = one shop
- Each MySQL database = that shop’s own cash register
- Kafka = internal mail between shops (“order packed”, “payment failed”)
- Next.js storefront = the public website shoppers see
- Administrator service = the staff office, not a public store
Why bother? Because a marketplace has three kinds of users with three kinds of risk.
- Shoppers want fast product pages and a checkout that is not charged twice.
- Sellers want to apply, open a store, publish products, and see their own orders and stock.
- Admins want to approve sellers, freeze bad accounts, and look at orders without logging into fourteen databases.
Search can be slow while checkout still works. Email can be down while payment still succeeds. Seller approval should not lock the whole catalog. Separate services keep those failures from becoming one giant outage.
What you see after you start Docker
After docker compose up is healthy, open these on your own computer (localhost is not linked on purpose — search engines should not crawl it):
- Shop website (Next.js) — port 3000
- Admin panel (AdminLTE 4) — port 3010
- API gateway — port 8080, path
/api/v1 - Health dashboard — port 8080, path
/dashboard - phpMyAdmin — port 8181
- Grafana / Prometheus — ports 3001 and 9091
The first time MySQL is empty, Flyway creates tables and demo users. All three demo accounts use the same local-only password Password123!:
customer@demo.com— shopperseller@demo.com— selleradmin@demo.com— admin panel
Those accounts are for your laptop. Do not use them on a public server. Put real secrets in .env before you expose the stack.
The technology, in one pass
Every backend module is Java 21 and Spring Boot 3. That keeps the programming style the same even though the processes are separate.
- Spring Cloud Gateway — one public URL for APIs
- MySQL 8 + Flyway — one schema per service; schema changes are SQL files, not “edit production by hand”
- Redis — fast cart data
- Apache Kafka — events after something important already saved
- OpenSearch — product search, not the live product tables
- Next.js — customer UI
- Thymeleaf + AdminLTE 4 — admin UI
- Docker Compose — start the mall with one command
Shared Java types (API wrappers, events, JWT helpers) live in common-lib. That library is a toolbox, not a dumping ground for business logic.
Third-party docs (nofollow): Spring Boot, Docker Compose, Apache Kafka.
How a request actually travels
Imagine you click “Add to cart” on the website.
- The browser talks only to the gateway (port 8080).
- The gateway checks the JWT if the route is protected.
- It throws away any fake “I am user X” headers from the client.
- It writes the real user id and role from the token.
- It forwards the call to cart-service.
- Cart-service reads and writes only
cart_db(and Redis). It does not opencatalog_db.
The admin panel is the same idea with a twist. It is a BFF (backend for frontend): a small Spring app that serves HTML and calls the gateway for you. It has no admin database. Staff log in, the JWT sits in an HttpOnly cookie, and the panel calls the same /api/v1/admin/... APIs Postman can call.
A simple map:
Shop website :3000 Admin office :3010
\ /
\ /
\ /
\ /
API gateway :8080
|
|
|
auth, catalog, seller, order, payment, cart, ...
|
|
|
MySQL (14 separate databases)
Kafka mail | Redis | OpenSearch
Notice the important split: one MySQL process on your laptop, but fourteen databases inside it.
That is cheaper to run locally. The rule is still: catalog-service never runs SQL against order_db.
When to call, when to send mail
Two shops in a mall can either phone each other (wait for an answer) or send a note (keep working).
Phone = REST (Feign). Use it when you cannot continue without the answer. Example: checkout must know “is this coupon valid?” and “is this SKU reservable?” before it creates an order.
Mail = Kafka. Use it when the sender should not wait. Example: the order is already saved. Email, search index, and shipping can catch up. If email is down, the customer still has an order.
Outbox is the boring-but-important trick. If you save the order and then try to send Kafka in the next line of Java, the process can die in between. You get an order with no event. Outbox means: in the same database transaction, write the order and a row that says “please publish this event.” A small poller sends Kafka later and ticks the row done.
Checkout is still a distributed transaction (stock + coupon + payment + order). This project does not use two-phase commit. Each service commits locally. If payment fails, a compensation event tells inventory to release the reservation. That pattern is a saga, done as choreography (events), not a central boss process.
Who owns what
You do not need to memorize every port. Remember the owner of the data:
- auth-service — login, register, refresh token, user roles
- user-service — profile, addresses, wishlist
- seller-service — seller application, store, approve / reject / suspend
- catalog-service — categories, brands, products, SKUs
- inventory-service — warehouses, quantity, reservations
- cart-service — guest and logged-in carts
- order-service — orders and per-seller sub-orders
- payment-service — payment intents, webhooks, refunds
- shipping-service — shipments and tracking
- notification-service — email / in-app messages
- review-service — ratings and reviews
- promotion-service — coupons and discount math
- search-service — search index
- recommendation-service — “you may also like”
Plus api-gateway (door), frontend (shop window), administrator-service (staff office, no database).
One extra rule that saves you later: at checkout, order-service copies the price and address onto the order. If the seller changes the price tomorrow, yesterday’s order does not magically change.
Login, in human terms
Early in the project, APIs were left open so the wiring could be finished. That is not how it ships now.
A shopper logs in like this:
- Website sends email and password to
/api/v1/auth/login. - auth-service returns a short-lived access JWT and a longer refresh token stored on the server.
- The website sends
Authorization: Bearer …on later calls. - The gateway checks the signature, then stamps the real user id and role on the request.
- The target service checks the JWT again (belt and suspenders) and checks the path (customer vs seller vs admin).
Roles in this marketplace:
- CUSTOMER — own profile, cart, orders, reviews, payments
- SELLER / SELLER_STAFF — own store, products, stock, seller orders
- SUPPORT — read-only admin APIs
- ADMIN — full admin APIs and the AdminLTE panel
Anyone can hit login, register, health, public catalog, search, public reviews, and a guest cart without a token. Almost everything else needs a token.
Honest limitation: seller id is still often a header, not a field inside the JWT. Seller routes require a seller role, but the cleaner design is “look up the shop from the logged-in user.” That is a good future patch, not a mystery.
Checkout without trusting the browser
The dangerous idea in ecommerce is letting the browser say “I paid” or “this costs $1.” This project does not do that.
A typical path:
- Cart is loaded from cart-service.
- Coupon is checked with promotion-service (if any).
- Inventory reserves stock.
- Order is created in
PENDING. - payment-service creates a payment intent.
- The payment provider calls a webhook.
- payment-service verifies the signature, ignores duplicate event ids, then publishes paid or failed.
- If paid, the order can move to
CONFIRMED. If failed, stock is released.
After that, the seller pack-and-ship path is a simple state machine. Cancel and refund are real states, not “delete the row.”
payment webhook OK
|
v
+---------+ +-----------+ +------------+ +---------------+
| PENDING |--->| CONFIRMED |--->| PROCESSING |--->| READY_TO_SHIP |
+---------+ +-----------+ +------------+ +-------+-------+
| | | |
| | | v
| | | +---------+
| | | | SHIPPED |
| | | +----+----+
| | | |
| | | v
| | | +-----------+
| | | | DELIVERED |
| | | +-----------+
| | |
+---------------+----------------+-----> CANCELLED
| |
+------------------------> REFUNDED (after delivery, if approved)
Who moves the status?
PENDING -> payment-service event "PaymentCompleted"
CONFIRMED -> seller accepts the sub-order
PROCESSING -> seller packed the box
READY_TO_SHIP -> carrier pickup
SHIPPED -> tracking says out for delivery / in transit
DELIVERED -> delivery confirmation
CANCELLED -> customer / admin / timeout before ship
REFUNDED -> payment-service refund webhook
Keep history. Webhooks and Kafka consumers must be safe to run twice (idempotent).
A full day in the life of one order
This is the story of one checkout, written as a sequence so you can see which service talks and which only listens.
Shopper "Amina" (CUSTOMER)
has SKU-BLUE-M from Seller S1
and SKU-CABLE from Seller S2
coupon WELCOME10
T0 Browser
GET /api/v1/cart
-> gateway -> cart-service
cart_db + Redis return 2 lines
T1 Browser
POST /api/v1/orders/checkout
body: address snapshot, couponCode, cart token
-> gateway -> order-service
T2 order-service (synchronous phones)
|-- Feign GET promotion-service "is WELCOME10 valid for this total?"
|-- Feign POST inventory-service "reserve SKU-BLUE-M qty 1"
|-- Feign POST inventory-service "reserve SKU-CABLE qty 1"
|-- INSERT order status = PENDING
|-- INSERT sub_order S1 status = PENDING
|-- INSERT sub_order S2 status = PENDING
|-- INSERT order_item x2 unit_price copied from catalog at this moment
|-- INSERT outbox row type = OrderCreated
COMMIT order_db <-- if this fails, no Kafka, no payment
T3 Outbox poller (order-service)
reads unpublished outbox
produces Kafka topic "order.created"
{
"eventId": "e-100",
"orderId": "ord-55",
"buyerId": "user-Amina",
"subOrders": [
{ "sellerId": "S1", "items": ["SKU-BLUE-M"] },
{ "sellerId": "S2", "items": ["SKU-CABLE"] }
]
}
T4 Kafka consumers (async, they do not block checkout)
notification-service "Your order was placed"
search-service no-op (order is not a product)
recommendation-service record interaction
T5 order-service
Feign POST payment-service create PaymentIntent
payment_db: status = REQUIRES_ACTION
T6 Payment provider (Stripe or test double)
Shopper pays
HTTP POST /api/v1/payments/webhooks
Header: Stripe-Signature: t=...,v1=...
T7 payment-service
verify signature
if eventId already processed -> return 200 (duplicate, ignore)
UPDATE payment PAID
INSERT outbox PaymentCompleted
COMMIT
T8 Kafka topic "payment.completed"
order-service PENDING -> CONFIRMED (both sub-orders)
inventory-service convert reservation -> sold (or keep reserved until ship)
notification "Payment received"
T9 Seller S1 dashboard
POST /api/v1/sellers/me/orders/{id}/accept
sub-order S1 -> PROCESSING
T10 Seller S1
POST .../pack -> READY_TO_SHIP
shipping-service create shipment, tracking MP123
T11 Kafka "shipment.created"
notification "Your blue shirt is on the way"
order-service sub-order S1 -> SHIPPED
T12 Seller S2 is slower
same path, later
Amina sees one parent order with two timelines
If T6 had failed (card declined):
Kafka "payment.failed"
inventory RELEASE reservation SKU-BLUE-M and SKU-CABLE
order PENDING -> CANCELLED
notification "Payment failed, stock released"
How a cart with two sellers is split
Amazon-style marketplaces do not create one box for two independent shops. They create one parent order and one sub-order per seller.
Cart (cart_db)
line 1 SKU-BLUE-M seller S1 qty 1 (display price only)
line 2 SKU-CABLE seller S2 qty 1
After checkout, order_db looks like this:
orders
id ord-55
buyer_id user-Amina
status CONFIRMED
currency USD
grand_total 42.90 <-- after WELCOME10
ship_to_json { "line1": "...", "city": "Dhaka" } snapshot
sub_orders
id so-1 order_id ord-55 seller_id S1 status PROCESSING
id so-2 order_id ord-55 seller_id S2 status PENDING
order_items
sku_code SKU-BLUE-M unit_price 29.99 qty 1 sub_order so-1
sku_code SKU-CABLE unit_price 19.99 qty 1 sub_order so-2
Seller S1 can pack so-1 without waiting for S2.
Refunds can hit one sub-order only.
Inventory reservations are per SKU, so S1 stock is independent of S2.
What a JWT looks like (conceptually)
The access token is not magic. It is three Base64 parts: header, claims, signature. After the gateway verifies the signature, it trusts the claims — not the headers the browser sent.
HTTP request from the storefront:
POST /api/v1/cart/items HTTP/1.1
Host: localhost:8080
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLUFtaW5hIiwiZW1haWwiOiJjdXN0b21lckBkZW1vLmNvbSIsInJvbGUiOiJDVVNUT01FUiIsImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoxNzAwMDAwOTAwfQ.signature
Content-Type: application/json
{ "skuId": "...", "quantity": 1 }
Decoded payload (example, not a real secret):
{
"sub": "user-Amina",
"email": "customer@demo.com",
"role": "CUSTOMER",
"iat": 1700000000,
"exp": 1700000900
}
Gateway then forwards to cart-service as:
POST /api/v1/cart/items
Authorization: Bearer (same)
X-User-Id: user-Amina <-- overwritten from "sub"
X-User-Role: CUSTOMER
X-User-Email: customer@demo.com
If the browser had sent X-User-Id: user-Hacker, that header is discarded.
Refresh token is NOT in this JWT. It is an opaque string stored in auth_db
and rotated when POST /api/v1/auth/refresh is called.
The admin panel (staff office)
Admins should not live in phpMyAdmin. The administrator app is Spring Boot + Thymeleaf + AdminLTE 4 on port 3010.
- No
administrator_db. - Login goes through the gateway. Only
ADMINmay continue. - The JWT is an HttpOnly cookie (JavaScript on the page cannot steal it as easily as
localStorage). - Pages other than login and static CSS/JS require that cookie.
Screens you can actually use: dashboard (which services are up), products, sellers, users, orders. Product photos are URL fields in this version — no file upload yet — so the admin app stays stateless.
How to run it
You need Docker. You need JDK 21 and Maven only if you change Java and rebuild JARs.
# 1) Get the code git clone https://github.com/needyamin/spring-boot-microservices-ecommerce.git cd spring-boot-microservices-ecommerce # 2) Local secrets (never commit the real .env) cp .env.example .env # edit JWT_SECRET, DB passwords, Stripe keys if you have them # 3) First Java package so Dockerfiles have JARs to copy mvn -DskipTests package # 4) Start the mall docker compose up -d --build # 5) Watch health docker compose ps docker compose logs -f api-gateway docker compose logs -f administrator-service # 6) Stop without deleting shop data docker compose down # 7) Nuclear reset (MySQL + Kafka + Redis volumes) docker compose down -v docker compose up -d --build
Important detail: Java images copy JARs from each module’s target folder. After you change Java:
# Example: you changed order-service checkout mvn -pl order-service -am -DskipTests package docker compose up -d --build order-service api-gateway # Frontend only docker compose up -d --build frontend # Admin BFF only (gateway must already be up) mvn -pl administrator-service -am spring-boot:run # then open port 3010
If Kafka complains about a cluster id after you wiped volumes, delete the Kafka volumes and start Kafka again. Do not delete the MySQL volume unless you want an empty shop.
Rebuild only the shop UI: docker compose up -d --build frontend.
Do not run npm run dev at the same time as that container — both want port 3000.
API examples you can paste into Postman
Base URL is the gateway: http://localhost:8080. Import marketplace-api.postman_collection.json from the repo, or start with these.
1. Login (customer)
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "customer@demo.com",
"password": "Password123!"
}
Example success body (shape):
{
"success": true,
"message": "Login successful",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiJ9...",
"refreshToken": "opaque-refresh-string",
"tokenType": "Bearer",
"expiresIn": 900,
"user": {
"id": "a1111111-1111-4111-8111-111111111101",
"email": "customer@demo.com",
"role": "CUSTOMER"
}
}
}
2. Public product list (no token)
GET /api/v1/products?page=0&size=20&sort=createdAt,desc
{
"success": true,
"data": {
"content": [
{
"id": "...",
"name": "Wireless headphones",
"slug": "wireless-headphones",
"minPrice": 29.99,
"status": "PUBLISHED"
}
],
"page": 0,
"size": 20,
"totalElements": 8
}
}
3. Admin: list users (ADMIN token required)
GET /api/v1/admin/users?page=0&size=20
Authorization: Bearer eyJhbGciOi...
PUT /api/v1/admin/users/{userId}/status
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
{
"status": "SUSPENDED"
}
POST /api/v1/admin/sellers/{sellerId}/approve
Authorization: Bearer eyJhbGciOi...
Typical HTTP codes: 200 OK, 201 created, 400 validation, 401 missing/bad token, 403 wrong role, 404 unknown id, 409 conflict (duplicate email, coupon already used).
Kafka topics (the internal mail room)
Names vary slightly in code, but the conversations look like this:
Producer Topic / event Consumers --------- -------------- --------- order-service OrderCreated notification, recommendation payment-service PaymentCompleted order (confirm), inventory payment-service PaymentFailed order (cancel), inventory (release) catalog-service ProductPublished search-service (index), recommendation shipping-service ShipmentCreated notification, order (SHIPPED) shipping-service ShipmentDelivered order (DELIVERED), notification seller-service SellerApproved notification (seller email) Each event JSON should include: eventId (uuid — consumer stores it so retries are no-ops) occurredAt (UTC) payload (orderId, sellerId, skuId, amounts...) Consumer rule: if (already_processed(eventId)) return; do work; save eventId;
Flyway: how a table is born
You never “create table” by hand in phpMyAdmin for this project. You add a file:
auth-service/src/main/resources/db/migration/
V1__init_auth_schema.sql
V2__demo_data.sql
V3__admin_user.sql
V4__fix_admin_password.sql
Example (simplified):
-- V1__init_auth_schema.sql
CREATE TABLE users (
id CHAR(36) NOT NULL,
email VARCHAR(255) NOT NULL,
password_hash VARCHAR(100) NOT NULL,
role VARCHAR(32) NOT NULL,
status VARCHAR(32) NOT NULL,
created_at TIMESTAMP(6) NOT NULL,
updated_at TIMESTAMP(6) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_users_email (email)
);
Rules:
* File name: V{number}__short_description.sql
* Never edit V1 after it has run on a machine (add V5 instead)
* Demo users belong in V2/V3, not in application.yml
Folder map
spring-boot-microservices-ecommerce/ ├── pom.xml parent Maven build (Java 21) ├── docker-compose.yml the whole mall ├── .env.example ├── marketplace-api.postman_collection.json ├── ARCHITECTURE.md ├── CONTRIBUTING.md │ ├── common-lib/ DTOs, events, JWT helpers ├── api-gateway/ :8080 Spring Cloud Gateway ├── auth-service/ :8081 auth_db ├── user-service/ :8082 user_db ├── seller-service/ :8083 seller_db ├── catalog-service/ :8084 catalog_db ├── inventory-service/ :8085 inventory_db ├── cart-service/ :8086 cart_db ├── order-service/ :8087 order_db ├── payment-service/ :8088 payment_db ├── shipping-service/ :8089 shipping_db ├── notification-service/ :8090 notification_db ├── review-service/ :8091 review_db ├── promotion-service/ :8092 promotion_db ├── search-service/ :8093 search_db ├── recommendation-service/ :8094 recommendation_db ├── administrator-service/ :3010 NO database (BFF) ├── frontend/ :3000 Next.js │ ├── infrastructure/ │ ├── docker/mysql/ CREATE DATABASE auth_db, catalog_db, ... │ └── monitoring/ Prometheus + Grafana └── docs/ auth, payments, orders, API notes
If you send a pull request: one service at a time, constructor injection, DTOs on APIs, Flyway for schema, tests included. Do not add a database to the admin BFF.
When something is red in docker compose ps
Symptom What to try first
------- -----------------
api-gateway restarting mvn -DskipTests package (missing JAR)
docker compose logs api-gateway
auth-service 500 on login Flyway not applied? check auth_db.flyway_schema_history
JSON body must be {"email":"...","password":"..."}
admin panel "Please sign in" Spring Security default login stole POST /login
administrator-service must disable formLogin
admin 3010 already in use stop leftover java.exe / old container
docker compose up -d administrator-service
products 404 via gateway Path=/api/v1/admin/products AND /products/**
rebuild api-gateway + catalog-service
Kafka InconsistentClusterId docker compose rm -f kafka
docker volume rm ..._kafka_data
(do not delete mysql_data unless you want empty shop)
frontend build fails unused imports / missing API helpers in Next.js
then docker compose up -d --build frontend
port 3000 busy leftover npm run dev (node.exe)
Rules worth stealing for your own project
- Controllers do not contain business rules.
- Never return a JPA entity from a REST endpoint.
- Public APIs stay under
/api/v1. - The browser is not the source of truth for price, stock, or payment status.
- Use UTC. Every table needs id, created_at, updated_at.
- Webhooks and Kafka consumers must tolerate duplicates.
- Secrets stay in
.env, not in git.
Gateway route map (what the door allows)
You can think of the gateway as a receptionist with a list. This is the idea, not every line of YAML:
Incoming path Who may call Goes to ------------- ------------ ------- POST /api/v1/auth/login anyone auth-service POST /api/v1/auth/register anyone auth-service POST /api/v1/auth/refresh anyone (refresh body) auth-service GET /api/v1/products/** anyone catalog-service GET /api/v1/categories/** anyone catalog-service GET /api/v1/search/** anyone search-service GET /api/v1/cart/** guest or CUSTOMER cart-service POST /api/v1/cart/** guest or CUSTOMER cart-service POST /api/v1/orders/checkout CUSTOMER order-service GET /api/v1/orders/me CUSTOMER order-service GET /api/v1/sellers/me/** SELLER, SELLER_STAFF seller / catalog / order POST /api/v1/sellers/me/products SELLER catalog-service GET /api/v1/admin/** ADMIN, SUPPORT (GET) matching domain service POST /api/v1/admin/** ADMIN only matching domain service PUT /api/v1/admin/** ADMIN only matching domain service GET /api/v1/health/** anyone gateway aggregator GET /dashboard anyone gateway HTML Anything else with a missing/invalid JWT -> 401 Wrong role -> 403 Unknown path -> 404
How I would test a change
Example: you changed how coupons apply at checkout. Do not start with Kubernetes. Start small.
1. Unit test in promotion-service
given coupon WELCOME10 and subtotal 50
expect discount 5.00
given expired coupon
expect 400
2. Unit test in order-service
mock PromotionClient and InventoryClient
checkout copies unit prices onto order_items
outbox row is written in the same transaction
3. Integration (Testcontainers MySQL)
Flyway V2 demo data loads
POST /api/v1/auth/login as customer@demo.com
POST /api/v1/orders/checkout
GET /api/v1/orders/{id} status PENDING or CONFIRMED
4. Compose
mvn -pl order-service,promotion-service -am test
mvn -DskipTests package
docker compose up -d --build order-service promotion-service api-gateway
use Postman collection folder "Orders"
If the unit tests fail, do not debug Docker yet.
If unit tests pass and Compose fails, look at gateway routes and JWT_SECRET mismatch.
What you could build next
The repo is a teaching mall, not a finished Amazon. Sensible follow-ups if you fork it:
- Put
sellerIdin the JWT and stop trustingX-Seller-Id. - Replace URL-only product images with S3 uploads.
- Add real Stripe keys and webhook signature secrets in
.env. - Per-seller payouts (a new ledger service — do not stuff it into payment-service forever).
- Rate limits on the gateway (login brute force).
- OpenTelemetry traces so one checkout shows as one waterfall across services.
Suggested first PR for a newcomer: feature/seller-service/seller-id-from-jwt 1. auth-service add claim sellerId when role is SELLER 2. common-lib gateway/resource-server copy claim to X-Seller-Id 3. seller-service ignore client-supplied X-Seller-Id if JWT present 4. tests CUSTOMER token cannot hit /sellers/me 5. Postman update seller folder
FAQ
Is this free?
Yes. MIT License. You run it. You keep your own secrets.
How many services?
Fourteen domain services, plus gateway, Next.js storefront, and the admin BFF.
Is this Shopify?
No. It is source code, not a hosted product.
How does admin login work?
The admin app posts to gateway login, allows ADMIN only, and stores the JWT in an HttpOnly cookie.
Why fourteen MySQL databases on one server?
So your laptop stays simple, while the code still obeys “do not query another service’s tables.” In production you can split the servers without rewriting the apps.
Why does checkout not wait for email?
Email is Kafka. The order is already committed. If SMTP is down, the shopper still has an order.
Can I run only catalog?
You can start MySQL, Kafka, Redis, gateway, auth, and catalog. Other pages will fail. That is expected — the mall is missing shops, not a monolith with optional classes.
Closing
If you want to learn Spring Boot microservices ecommerce by reading real module boundaries — not slides — clone the repo, start Compose, open port 3000, then sign in on port 3010 as the seeded admin.
Download the Spring Boot microservices ecommerce project on GitHub · More from the author on GitHub
This post is an original overview of a public open-source project. It is educational, not legal or payment advice. Demo emails and passwords are for a local Docker database only. Spring, Docker, Kafka, Next.js, and AdminLTE are trademarks of their owners.

(0) Comments on "Spring Boot Microservices Ecommerce: Multivendor Marketplace Guide"
* Most comments will be posted if that are on-topic and not abusive