A multi-vendor commerce platform connecting buyers, sellers, and administrators through dedicated storefronts, dashboards, payments, chat, and personalized discovery.
Built as a personal project, ZUZI explores how a multi-vendor marketplace can support distinct buyer, seller, and platform-administration journeys within one codebase. Buyers browse shops and products, sellers manage catalogues and orders, and administrators review platform activity and moderate content.
The implemented workflows cover email-verified authentication, profile and address management, product and event management, server-backed carts, discount validation, Stripe payment processing, order tracking, reviews, notifications, seller analytics, and buyer-seller chat. Behaviour events feed product, shop, and user analytics, which support cached personalized recommendations with fallback ranking.
The system is organized as an Nx monorepo with three Next.js applications, an Express API gateway, independently runnable backend services, and shared packages for Prisma, authentication, pricing, errors, media, email, and Kafka. MongoDB provides persistence, Redis supports transient state, and Kafka decouples analytics, notification, and chat persistence workloads.
Technology stack
Related projects
More work to explore
GedaraStay
Aug 2025 – Oct 2025
A marketplace for verified Sri Lankan homestays, supporting guest bookings, host onboarding, admin approval, and test-mode payments.
Supports shops, rich product listings, filters, flash-sale events, discount codes, cart synchronization, Stripe payments, commissions, and order tracking.
Provides real-time buyer-seller chat with Socket.IO, Redis presence and buffering, Kafka-backed batch persistence, typing indicators, seen states, and image uploads.
Aggregates user, product, and shop behaviour through Kafka and generates cached TensorFlow.js recommendations with weighted and popularity fallbacks.
Restricts reviews to eligible purchasers using hashed, expiring review tokens, with seller replies, reporting, rating aggregation, and admin moderation.
Architecture
Three Next.js apps for buyers, sellers, and administrators send HTTP requests through an Express API gateway to independently runnable domain services. Services share a Prisma-backed MongoDB schema, use Redis for transient state, Kafka for asynchronous workflows, Socket.IO for live chat, and Stripe, ImageKit, and SMTP integrations.
Three Next.js App Router clients separate buyer, seller, and administrator workflows.
An Express API gateway centralizes CORS, rate limiting, cookie parsing, and reverse-proxy routing to domain services.
Authentication, catalogue, order, admin, chat, recommendation, notification, and analytics run as separate Nx applications.
Prisma provides shared MongoDB persistence, while Redis supports payment sessions, chat presence, and buffered chat state.
Kafka carries analytics, notification, and chat events; Socket.IO handles live messaging, with Stripe and ImageKit as external integrations.
Project case study
ZUZI — E-Commerce Microservices Monorepo
A full-stack, production-grade e-commerce platform built on a microservices architecture, managed as an Nx monorepo. ZUZI connects buyers, sellers, and platform administrators through a suite of independent backend services and three dedicated Next.js frontend applications.
Zuzi is an Nx monorepo with three Next.js frontend apps, an Express API Gateway, independently runnable backend microservices, shared packages for cross-service infrastructure, and external systems for payments, messaging, email, file storage, caching, and analytics. Frontend HTTP traffic is routed through the API Gateway, while realtime chat uses Socket.IO directly against the chatting service.
The single entry point for all client traffic. Handles cross-cutting concerns before proxying requests downstream.
Responsibilities:
Reverse proxying to all backend microservices
Global rate limiting: 100 req/15 min (unauthenticated), 1000 req/15 min (authenticated)
CORS enforcement for localhost:3000, localhost:3001, localhost:3002
Cookie parsing and JSON body parsing
Site configuration initialization on startup
Proxy Routes:
Prefix
Downstream Service
Port
/
Auth Service
6001
/product
Product Service
6002
/order
Order Service
6004
/admin
Admin Service
6005
/chat
Chatting Service
6006
/recommendation
Recommendation Service
6007
/notification
Health check endpoint: GET /gateway-health
Auth Service (port 6001)
Handles all authentication and user profile management for both customers and sellers. Exposes a Swagger UI at /api-docs.
User Authentication Flow:
POST /api/user-registration — Register; sends OTP email with activation link
POST /api/verify-otp — Verify email OTP to activate account
POST /api/login-user — Login; issues JWT access + refresh token cookies
POST /api/refresh-token — Silently rotate access token
POST /api/logout — Clear auth cookies
Seller Authentication Flow:
POST /api/seller-registration — Register seller; sends OTP email
POST /api/seller-verify-otp — Verify OTP
POST /api/login-seller — Login as seller
POST /api/seller/shop — Create seller shop (authenticated)
POST /api/seller/stripe/connect — Connect Stripe account for payouts
GET /api/seller/status — Check onboarding status
Password Reset:
POST /api/forgot-password — Send reset email
GET /api/password-reset/verify/:token — Verify reset token
POST /api/password-reset/:token — Set new password
Profile Management (authenticated):
GET/PATCH /api/me — Get/update profile
GET /api/me/addresses — List shipping addresses
POST /api/me/addresses — Add shipping address
PATCH /api/me/addresses/:id — Update address
DELETE /api/me/addresses/:id — Delete address
PATCH /api/me/addresses/:id/default — Set default address
Token Strategy: JWT access tokens (short-lived) stored in HttpOnly cookies, with refresh token rotation. Both user and seller roles share the same token structure with a role claim.
Product Service (port 6002)
The core catalogue service managing products, shops, reviews, events, and discount codes. Includes a cron job that permanently deletes soft-deleted products after a 24-hour restore window and cleans up their ImageKit assets.
Public Endpoints:
GET /api/get-products — List all products
GET /api/get-filtered-products — Filter/sort/paginate products
GET /api/search-products — Full-text product search
GET /api/get-product/:slug — Product detail by slug
GET /api/events — Time-bounded event/flash-sale products
GET /api/shops — List shops
GET /api/shops/:shopId — Shop detail
GET /api/shops/:shopId/products — Shop's products
GET /api/shops/:shopId/events — Shop's events
GET /api/shops/:shopId/reviews — Shop reviews
GET /api/shops/:shopId/review-summary — Aggregated shop rating
GET /api/top-shops — Top-rated shops
GET /api/products/:productId/reviews — Product reviews
GET /api/products/:productId/review-summary — Aggregated product rating
GET /api/get-categories — Available categories
Seller Endpoints (authenticated):
POST /api/create-product — Create product with images
PUT /api/update-product — Update product
POST /api/delete-product — Soft-delete product
PATCH /api/restore-product — Restore within 24h window
GET /api/get-shop-products — List own products
GET /api/seller/products/:id — Get own product detail
PATCH /api/seller/products/:id — Update own product
POST /api/upload-image — Upload product image (multipart/form-data → ImageKit)
POST /api/delete-image — Remove product image from ImageKit
Real-time messaging between buyers and sellers, using Socket.io for delivery and Kafka for durable persistence.
HTTP Endpoints (authenticated):
GET /api/conversations — List own conversations
GET /api/conversations/:id/messages — Load message history (paginated)
POST /api/upload — Upload chat image (multipart/form-data → ImageKit)
Socket.io Events:
Event (emit)
Direction
Description
join_conversation
Client → Server
Join a conversation room
send_message
Client → Server
Send a text or image message
typing
Client → Server
Broadcast typing indicator
mark_seen
Client → Server
Mark messages as seen
message_received
Server → Client
Deliver incoming message
message_seen
Server → Client
Seen acknowledgement
Persistence Strategy: Messages are buffered in Redis and flushed to MongoDB via a Kafka consumer every CHAT_MESSAGE_FLUSH_INTERVAL_MS (default 3000ms) or when the batch reaches CHAT_MESSAGE_FLUSH_MAX_BATCH (default 100). This decouples write throughput from socket latency.
Recommendation Service (port 6007)
ML-powered product recommendations using a hybrid TensorFlow.js model with a weighted-baseline fallback.
Endpoints:
GET /api/recommendations — Get personalised product recommendations (supports pagination; works for anonymous users with content-based fallback)
POST /api/recommendations/train — Trigger model training for the authenticated user
Training Pipeline:
Collects the user's tracked actions from userAnalytics (views, cart adds, wishlist, purchases)
Builds a feature vocabulary from product metadata (category, subcategory, brand, shop, tags, price, ratings)
Constructs a binary classification training dataset (positive = interacted products, negative = sampled non-interacted)
Trains a dense neural network via TensorFlow.js with configurable epochs and batch size
Falls back to a weighted-baseline algorithm (scoring by interaction type weight × recency) if insufficient data or TF.js failure
Stores top-N recommendation IDs in userAnalytics.recommendations with a TTL and model version
Minimum actions required: configurable via MINIMUM_TRAINING_ACTIONS constant (default enforced internally).
Logger Service (port 6008)
Placeholder service reserved for centralised structured logging integration (e.g., ELK Stack, Datadog). Currently serves a health-check response.
Notification Service (port 6009)
Delivers in-app notifications to users and sellers. Consumes Kafka events for async notification creation and exposes REST endpoints for notification management.
HTTP Endpoints:
GET /api/health — Service health
POST /api/internal/notifications — Internal API for services to create notifications (protected by NOTIFICATION_INTERNAL_TOKEN)
GET /api/notifications — List notifications for authenticated user (paginated)
GET /api/notifications/unread-count — Count unread notifications
PATCH /api/notifications/:id/read — Mark notification as read
PATCH /api/notifications/read-all — Mark all notifications as read
DELETE /api/notifications/:id — Delete a notification
Kafka Consumer: Listens on NOTIFICATION_KAFKA_TOPIC (default notification.events) and processes events published by the Order Service and other services to create notifications for order status changes, payment confirmations, new reviews, and more.
Kafka Service (consumer only)
A standalone Kafka consumer that processes user behaviour events to power analytics and the recommendation engine. It does not expose an HTTP port.
Eligibility-gated: only purchasers can review; checked against order history
Secure review tokens (publicId.secret format, SHA-256 hashed server-side) sent post-delivery with configurable 30-day expiry
Review request status lifecycle: Pending → Used / Expired / Revoked
Seller reply and report functionality
Admin moderation: publish, hide, or delete reviews
Rating aggregation on both product and shop level
Analytics
User behaviour events published to Kafka from the user-ui (product views, cart events, wishlist events, shop visits, purchases)
Analytics service consumes and aggregates into userAnalytics, productAnalytics, shopAnalytics, and shopDailyAnalytics
Device detection (ua-parser-js) and geolocation tracking on events
Seller-facing shop analytics dashboard showing visit trends and login/guest breakdowns
Notifications
Notification service consumes Kafka events for order confirmations, status changes, and review requests
Internal API allows other services to push notifications directly
Unread count badge and mark-read/mark-all-read functionality
Deduplication support via dedupeKey field
Getting Started
Prerequisites
Tool
Version
Purpose
Node.js
20 LTS
Runtime
npm
10+
Package manager
MongoDB
6+
Database (local or Atlas)
Redis
7+
Cache and session store
Apache Kafka
3+
Event streaming (optional for development)
Stripe CLI
Latest
Webhook forwarding in local dev
Kafka is optional in development. Set KAFKA_ENABLED=false in your .env to run without it. Use the dev:no-kafka script to exclude the kafka-service from the process group.
Installation
Shell
# Clone the repository
git clone https://github.com/your-org/ZUZI-ecommerce-microservices-monorepo.git
cd ZUZI-ecommerce-microservices-monorepo
# Install all dependencies
npm install
Environment Configuration
Shell
# Copy the example environment file
cp .env.example .env
# Open and fill in the required values (see Environment Variables Reference below)
nano .env
At minimum, configure:
DATABASE_URL — MongoDB connection string
REDIS_DATABASE_URI — Redis connection string
SMTP_* — Email credentials for OTP and password-reset emails
REGISTRATION_SECRET, ACCESS_TOKEN_SECRET, REFRESH_TOKEN_SECRET — JWT secrets (use strong random strings)
STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET — from the Stripe Dashboard
IMAGEKIT_PRIVATE_KEY — from the ImageKit Dashboard
ADMIN_SETUP_TOKEN, ADMIN_SEED_EMAIL, ADMIN_SEED_PASSWORD — for first admin bootstrap
Database Setup
Shell
# Generate the Prisma client from the schema
npx prisma generate
# Push the schema to MongoDB (creates collections and indexes)
npx prisma db push
Running the Project
Development — all services:
Shell
npm run dev
Development — without Kafka (recommended for local dev):
All requests go through the API Gateway on port 8090. Prefix paths with the gateway proxy path.
Service
Base URL via Gateway
Auth Service
http://localhost:8090/api/...
Product Service
http://localhost:8090/product/api/...
Order Service
http://localhost:8090/order/api/...
Admin Service
http://localhost:8090/admin/api/...
Chat Service
http://localhost:8090/chat/api/...
Recommendation Service
http://localhost:8090/recommendation/api/...
Notification Service
http://localhost:8090/notification/api/...
Swagger UI (Auth Service only): http://localhost:6001/api-docs
Authentication is passed via the access_token HttpOnly cookie set at login, or as an Authorization: Bearer <token> header.
Environment Variables Reference
Variable
Required
Description
Example
DATABASE_URL
✅
MongoDB connection string
mongodb+srv://user:pass@cluster.mongodb.net/zuzi
REDIS_DATABASE_URI
✅
Redis connection URI
redis://localhost:6379
SMTP_HOST
✅
SMTP server host
smtp.gmail.com
SMTP_PORT
✅
SMTP port
587
CI/CD
The project uses GitHub Actions (ci.yml) triggered on pushes to main and all pull requests.
Pipeline steps:
Checkout with full git history (for Nx affected detection)
Set up Node.js 20 with npm cache
npm ci — clean install
npx nx run-many -t lint test build typecheck — run all targets across affected projects in parallel
npx nx fix-ci — auto-fix CI issues where possible (always runs)
Nx Cloud integration is configured (nxCloudId in nx.json) for remote caching and optional distributed task execution. Distributed agents are commented out in the workflow but can be enabled by uncommenting the nx start-ci-run step.
Docker Support
Each backend service includes a generated Dockerfile (e.g., apps/auth-service/Dockerfile):
The Docker plugin is configured in nx.json under @nx/docker with docker:build and docker:run targets automatically inferred for each app.
Architecture Decisions
Monorepo with Nx — All services and packages live in a single repository, enabling shared TypeScript types, shared libraries (Prisma client, auth middleware, error classes, product pricing logic), atomic commits across service boundaries, and Nx's powerful affected-project detection for CI efficiency.
Shared Prisma schema — A single schema.prisma at the root covers all data models. Every service imports the same Prisma client from @libs/prisma, ensuring schema consistency without duplicated model definitions. MongoDB's flexible document model accommodates the embedded types (cartItem, userAnalyticsAction, ImageAsset) well.
API Gateway as sole ingress — All frontend traffic enters through a single Express proxy. Rate limiting, CORS, and cookie parsing are applied once rather than duplicated across nine services.
Kafka as the event backbone — User behaviour events and notifications use Kafka for durability and decoupling. The analytics pipeline is write-intensive and can lag without affecting the user experience. Chat messages are buffered in Redis and flushed through Kafka to MongoDB to achieve both low-latency delivery and durable storage.
TensorFlow.js for recommendations — Running TF.js in Node.js keeps the recommendation model co-located with its training data (already in MongoDB) without requiring a Python service or an external ML platform. The hybrid approach (TF.js score × 0.75 + baseline score × 0.25) provides graceful quality degradation.
Soft-delete with timed hard-delete — Products are soft-deleted (setting isDeleted = true and deletedAt) and permanently removed by a cron job 24 hours later, giving sellers a recovery window while avoiding orphaned ImageKit assets.
Secure review tokens — Rather than passing database IDs in review invitation links, the system generates a publicId.secret token. The tokenHash (SHA-256 of the full token) is stored in the database; the secret is never persisted. This prevents enumeration attacks on review requests.
Platform commission tracking — Every successful order generates a platformCommission record capturing the rate, base, amount, and seller-receivable amount at the time of transaction. This provides an immutable audit trail independent of any future rate changes.
The case-study preview is collapsed. Activate the button to make the full content available.
Challenges & learnings
Coordinating three frontend applications and multiple backend services without duplicating infrastructure was addressed through Nx projects, TypeScript path aliases, and shared packages for persistence, authentication, pricing, errors, messaging, email, and reusable UI controls.
Keeping chat responsive while retaining durable message history required separating live delivery from persistence. Socket.IO handles real-time events, Redis manages presence and buffered state, and Kafka-driven batching writes messages to MongoDB.
Protecting asynchronous checkout from duplicate or inconsistent orders required Stripe signature verification, duplicate payment checks, server-side recalculation of trusted totals, stock validation, and grouped Prisma writes.
Producing useful recommendations from limited behaviour data was addressed with minimum-action thresholds, TensorFlow.js training, cached recommendation IDs, and weighted or popularity-based fallbacks.
Future improvements
Add meaningful unit, integration, and end-to-end tests, remove permissive no-test behaviour, and require verified passing checks in CI.
Complete Google OAuth, the logger service, placeholder admin pages, and secondary profile and seller-metric sections.
Add Dockerfiles for the remaining services and provide Docker Compose or equivalent local orchestration with documented startup dependencies.
Introduce structured logging, request correlation, service readiness checks, and environment-controlled service URLs and CORS origins.
CodeVerse
Oct 2025 – Nov 2025
A full-stack developer Q&A platform where programmers can ask, answer, vote, save questions, explore tags, and generate AI-assisted responses.
A hands-on DevOps playground for developers learning to containerize an Express API and run replicated workloads on Kubernetes with Docker and Minikube.