A production-oriented event-driven microservices order management system built with NestJS, TypeScript, PostgreSQL, Apache Kafka, Redis, BullMQ, Prisma, and Docker.
This project is primarily designed as a practical backend engineering project to learn and implement real-world distributed-system concepts.
The system focuses on:
- Microservices Architecture
- Event-Driven Architecture
- Apache Kafka
- Transactional Outbox Pattern
- Database-per-Service
- Eventual Consistency
- Idempotent Consumers
- Distributed System Reliability
- Clean Architecture
- SOLID Principles
- Redis
- BullMQ
- Background Workers
- Retry & Dead Letter Queues
- Authentication & Authorization
- API Gateway
- Observability
- Dockerized Deployment
- Production-oriented Backend Design
The goal of this project is to build a realistic order-processing platform where different business capabilities are separated into independent microservices.
A typical order lifecycle will look like:
Customer
β
βΌ
API Gateway
β
βΌ
Order Service
β
βββ Create Order
βββ Save Order
βββ Create Outbox Event
β
βΌ
Kafka
β
βββββββ΄ββββββ
βΌ βΌ
Payment Inventory
Service Service
β β
βΌ βΌ
Payment Stock
Processing Reservation
β β
βββββββ¬ββββββ
βΌ
Kafka
β
βΌ
Order Service
β
βΌ
Order Confirmed
β
βΌ
Notification Service
β
βββ Email
βββ WebSocket
ββββββββββββββββ
β Client β
ββββββββ¬ββββββββ
β
βΌ
ββββββββββββββββββββ
β API Gateway β
β :3000 β
ββββββββββ¬ββββββββββ
β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β Auth Service β β Order Serviceβ β Inventory β
β β β β β Service β
ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββ¬ββββββββ
β β β
βΌ βΌ βΌ
βββββββββββ βββββββββββ βββββββββββββββ
β auth_db β βorder_db β β inventory_dbβ
βPostgres β βPostgres β β Postgres β
βββββββββββ ββββββ¬βββββ βββββββββββββββ
β
βΌ
ββββββββββββββββ
β Outbox β
β Events β
ββββββββ¬ββββββββ
β
βΌ
ββββββββββββββββ
β Kafka β
ββββββββ¬ββββββββ
β
βββββββββββββββββΌβββββββββββββββββ
β β β
βΌ βΌ βΌ
Payment Service Inventory Service Notification
β Service
βΌ β
payment_db β
βΌ
BullMQ
β
βΌ
Redis
β
βΌ
Workers
event-driven-order-system/
β
βββ apps/
β β
β βββ api-gateway/
β β
β βββ auth-service/
β β
β βββ order-service/
β β
β βββ payment-service/
β β
β βββ inventory-service/
β β
β βββ notification-service/
β
βββ libs/
β β
β βββ auth/
β βββ common/
β βββ events/
β βββ kafka/
β βββ redis/
β
βββ infrastructure/
β βββ docker/
β βββ kafka/
β βββ postgres/
β βββ redis/
β βββ nginx/
β
βββ docker-compose.yml
βββ nest-cli.json
βββ package.json
βββ pnpm-lock.yaml
βββ README.md
The API Gateway is the single public entry point for clients.
- HTTP API
- API versioning
- Authentication forwarding
- Authorization
- Request validation
- Rate limiting
- Routing
- Error handling
- Correlation ID
- WebSocket gateway
The API Gateway should remain thin.
It should not contain core business logic.
Client
β
API Gateway
β
Business Service
Responsible for:
- User registration
- Login
- Password hashing
- JWT access token
- Refresh token
- Logout
- Token rotation
- Role-based authorization
- User management
auth_db
βββ users
βββ refresh_tokens
βββ sessions
Auth Service owns its database.
No other service should directly access auth_db.
The Order Service is responsible for the complete order lifecycle.
- Create order
- Retrieve order
- List user orders
- Cancel order
- Order state management
- Create order items
- Publish order events
- Consume payment events
- Consume inventory events
order_db
βββ orders
βββ order_items
βββ outbox_events
βββ processed_events
Responsible for:
- Payment creation
- Payment processing
- Payment success
- Payment failure
- Refund
- Payment status
- Payment idempotency
- Publishing payment events
payment_db
βββ payments
βββ refunds
βββ outbox_events
βββ processed_events
Responsible for:
- Product management
- Stock management
- Stock reservation
- Stock release
- Stock adjustment
- Inventory consistency
- Publishing inventory events
inventory_db
βββ products
βββ inventory
βββ stock_reservations
βββ outbox_events
βββ processed_events
Responsible for:
- Kafka event consumption
- Email notifications
- WebSocket notifications
- Notification history
- Background jobs
- Retry handling
Flow:
Kafka
β
Notification Consumer
β
BullMQ
β
Redis
β
Notification Worker
β
Email / WebSocket
The project follows the Database-per-Service pattern.
Auth Service
β
auth_db
Order Service
β
order_db
Payment Service
β
payment_db
Inventory Service
β
inventory_db
Services must never directly query another service's database.
Order Service
β
SELECT FROM payment_db.payments
Order Service
β
Kafka Event
β
Payment Service
β
Payment DB
During development, a single PostgreSQL server/cluster can host multiple databases:
PostgreSQL
β
βββ auth_db
βββ order_db
βββ payment_db
βββ inventory_db
This keeps development infrastructure simple.
Later, production workloads can be separated into independent PostgreSQL instances/clusters if required.
One of the main learning goals of this project is implementing the Transactional Outbox Pattern.
Suppose Order Service does this:
1. Save Order β PostgreSQL β
2. Publish order.created β Kafka β
If Kafka fails, the order exists but the event is lost.
This creates inconsistency.
The database change and event are written inside the same PostgreSQL transaction.
BEGIN
INSERT INTO orders
INSERT INTO outbox_events
COMMIT
Both succeed or both fail.
Then a separate publisher sends the event to Kafka.
PostgreSQL
β
βΌ
outbox_events
β
βΌ
Outbox Publisher
β
βΌ
Kafka
Example:
outbox_events
βββ id
βββ aggregate_type
βββ aggregate_id
βββ event_type
βββ payload
βββ version
βββ created_at
βββ published_at
βββ attempts
βββ last_error
βββ locked_at
βββ locked_by
Example event:
{
"id": "event-id",
"aggregateType": "Order",
"aggregateId": "order-id",
"eventType": "order.created",
"version": 1,
"payload": {
"orderId": "order-id",
"userId": "user-id",
"total": 1500,
"items": []
},
"createdAt": "2026-08-09T00:00:00Z"
}Kafka is the central event backbone of the system.
Producer
β
βΌ
Kafka Topic
β
βββββββββββββββββ
βΌ βΌ
Consumer A Consumer B
Main events:
order.created
order.cancelled
order.confirmed
order.failed
order.completed
payment.created
payment.completed
payment.failed
payment.refunded
inventory.reserved
inventory.released
inventory.insufficient
Recommended topics:
order.events.v1
payment.events.v1
inventory.events.v1
notification.events.v1
For failed messages:
order.events.v1.DLQ
payment.events.v1.DLQ
inventory.events.v1.DLQ
POST /api/v1/orders
β
βΌ
API Gateway
β
βΌ
Order Service
β
βββ INSERT order
βββ INSERT order_items
βββ INSERT outbox_event
β
βΌ
COMMIT
β
βΌ
Outbox Publisher
β
βΌ
Kafka
β
βΌ
order.created
β β
βΌ βΌ
Payment Inventory
Service Service
β β
βΌ βΌ
payment.completed inventory.reserved
β β
βββββββ¬ββββββ
βΌ
Kafka
β
βΌ
Order Service
β
βΌ
Order CONFIRMED
β
βΌ
Notification Service
order.created
β
βΌ
Payment Service
β
βΌ
payment.failed
β
βΌ
Order Service
β
βΌ
Order FAILED
β
βΌ
Inventory Service
β
βΌ
Release Reservation
β
βΌ
Notification Service
This demonstrates eventual consistency and compensating actions.
Kafka provides at-least-once delivery in common production designs.
Therefore, consumers must be idempotent.
Example:
Kafka Event
β
βΌ
Consumer
β
βΌ
Check processed_events
β
βββ Event exists
β β
β Ignore
β
βββ Event does not exist
β
βΌ
Process Event
β
βΌ
Save processed_event
Example table:
processed_events
βββ event_id
βββ event_type
βββ processed_at
βββ consumer
The same event must not produce duplicate business effects.
Temporary failures should be retried.
Consumer
β
βΌ
Processing
β
βββ Success β Commit
β
βββ Failure
β
βΌ
Retry
β
βββ Success
β
βββ Failure
β
βΌ
DLQ
Example retry schedule:
Retry 1 β 1 second
Retry 2 β 5 seconds
Retry 3 β 30 seconds
Retry 4 β 2 minutes
Events that cannot be processed after the configured retry limit are moved to a DLQ.
Main Topic
β
Consumer
β
Retry
β
Retry
β
Retry
β
DLQ
DLQ messages should preserve:
- Original event
- Error message
- Retry count
- Consumer name
- Timestamp
The system should support idempotency for operations such as:
Create Order
Payment
Refund
Inventory Reservation
Example:
Idempotency-Key: abc123
Repeated requests using the same key should not create duplicate business operations.
Each business service should follow a clean architecture structure.
Example:
order-service/
src/
βββ modules/
β βββ order/
β β
β βββ domain/
β β βββ entities/
β β βββ value-objects/
β β βββ repositories/
β β
β βββ application/
β β βββ use-cases/
β β βββ dto/
β β
β βββ infrastructure/
β β βββ persistence/
β β βββ messaging/
β β
β βββ presentation/
β βββ controllers/
β βββ consumers/
β
βββ config/
βββ health/
βββ main.ts
The project intentionally follows SOLID.
Separate responsibilities:
OrderController
OrderService
OrderRepository
OrderEventPublisher
Example payment providers:
PaymentProvider
β
βββ StripeProvider
βββ PayPalProvider
βββ MockPaymentProvider
Implementations should be replaceable through abstractions.
Prefer small interfaces:
OrderReader
OrderWriter
OrderCanceller
instead of one huge interface.
Business logic depends on abstractions.
Order Use Case
β
βΌ
OrderRepository
β²
β
PrismaOrderRepository
Reusable cross-service utilities.
Possible contents:
common/
βββ constants/
βββ decorators/
βββ exceptions/
βββ filters/
βββ interceptors/
βββ pipes/
βββ types/
Only truly generic functionality should go here.
Shared authentication contracts/utilities.
Possible contents:
auth/
βββ decorators/
βββ guards/
βββ interfaces/
βββ types/
Business-specific authentication logic stays inside auth-service.
Shared domain event contracts.
Example:
events/
βββ order/
β βββ order-created.event.ts
β βββ order-cancelled.event.ts
β βββ order-confirmed.event.ts
β
βββ payment/
β βββ payment-completed.event.ts
β βββ payment-failed.event.ts
β
βββ inventory/
βββ inventory-reserved.event.ts
βββ inventory-released.event.ts
Reusable Kafka infrastructure.
kafka/
βββ kafka.module.ts
βββ kafka.producer.ts
βββ kafka.consumer.ts
βββ kafka.client.ts
βββ kafka.types.ts
Kafka infrastructure should be separated from business events.
Reusable Redis infrastructure.
redis/
βββ redis.module.ts
βββ redis.service.ts
βββ redis.types.ts
Redis can support:
- Cache
- BullMQ
- Rate limiting
- Distributed locks
Redis is not the primary source of truth.
POST /api/v1/auth/register
POST /api/v1/auth/login
POST /api/v1/auth/refresh
POST /api/v1/auth/logout
GET /api/v1/auth/meGET /api/v1/products
GET /api/v1/products/:productId
POST /api/v1/products
PATCH /api/v1/products/:productId
DELETE /api/v1/products/:productIdPOST /api/v1/orders
GET /api/v1/orders
GET /api/v1/orders/:orderId
POST /api/v1/orders/:orderId/cancelPOST /api/v1/orders/:orderId/payments
GET /api/v1/orders/:orderId/payments
GET /api/v1/payments/:paymentId
POST /api/v1/payments/:paymentId/refundGET /api/v1/notifications
GET /api/v1/notifications/unread
PATCH /api/v1/notifications/:id/read
PATCH /api/v1/notifications/read-allEvery service should expose:
GET /health
GET /health/live
GET /health/readyReadiness should verify dependencies such as:
PostgreSQL
Kafka
Redis
Expose:
GET /metricsMetrics can later be collected by Prometheus.
Important metrics:
HTTP request count
HTTP latency
HTTP error rate
Kafka consumer lag
Kafka throughput
Database connections
Redis usage
Queue size
Worker failures
Notification processing:
Kafka
β
Notification Consumer
β
BullMQ Queue
β
Redis
β
Notification Worker
β
Email Provider
Possible queues:
email
notifications
websocket
cleanup
BullMQ will provide:
- Background processing
- Retry
- Delayed jobs
- Failed jobs
- Job prioritization
The system should implement:
- JWT access tokens
- Refresh tokens
- Password hashing
- RBAC
- Request validation
- Rate limiting
- CORS
- Helmet
- Secure headers
- Environment-based secrets
- Service authentication
- Database least privilege
Roles:
USER
ADMIN
The system should implement:
Structured Logging
β
βΌ
Correlation ID
β
βΌ
API Gateway
β
βΌ
Microservice
β
βΌ
Kafka Event
β
βΌ
Consumer
A correlation ID should be propagated across the event lifecycle whenever possible.
Development environment:
Docker Compose
β
βββ PostgreSQL
βββ Redis
βββ Kafka
βββ Kafka UI
βββ Prometheus
βββ Grafana
Application services:
βββ api-gateway
βββ auth-service
βββ order-service
βββ payment-service
βββ inventory-service
βββ notification-service
Each service should manage its own environment configuration.
Example:
NODE_ENV=development
APP_NAME=order-service
PORT=3001
DATABASE_URL=postgresql://...
KAFKA_BROKERS=localhost:9092
KAFKA_CLIENT_ID=order-service
KAFKA_GROUP_ID=order-service-group
REDIS_HOST=localhost
REDIS_PORT=6379Never commit secrets to Git.
Use:
.env
.env.example
and secret management in production.
Test:
- Domain logic
- Use cases
- Services
- Event handlers
- Validation
Test:
- PostgreSQL repositories
- Kafka producers
- Kafka consumers
- Redis
- BullMQ
Test complete workflows.
Register
β
Login
β
Create Order
β
Payment
β
Inventory Reservation
β
Order Confirmation
β
Notification
The system should handle:
- Duplicate Kafka messages
- Kafka downtime
- Consumer crashes
- Database failures
- Network failures
- Payment failures
- Inventory failures
- Worker failures
- Notification failures
The design prioritizes:
At-Least-Once Delivery
+
Idempotent Consumers
+
Retry
+
DLQ
rather than assuming that every distributed operation can be exactly-once.
By completing this project, the following concepts should become practical rather than theoretical:
- Modules
- Dependency Injection
- Guards
- Interceptors
- Pipes
- Filters
- Custom decorators
- Configuration
- Microservices
- Transactions
- Isolation
- Indexing
- Constraints
- Locking
- Concurrency
- Migrations
- Schema design
- Relations
- Transactions
- Migrations
- Repository pattern
- Producers
- Consumers
- Topics
- Partitions
- Consumer Groups
- Offsets
- Rebalancing
- Retry
- DLQ
- Event ordering
- Eventual consistency
- Idempotency
- At-least-once delivery
- Failure handling
- Compensation
- Distributed transactions
- Outbox Pattern
- Caching
- Rate limiting
- Queue infrastructure
- Distributed locks
- Workers
- Retry
- Delayed jobs
- Failed jobs
- Job queues
- SOLID
- Clean Architecture
- Dependency Inversion
- Repository Pattern
- Event-Driven Architecture
- Database-per-Service
- API Gateway
- NestJS monorepo
- Configure applications
- Configure shared libraries
- ESLint
- Prettier
- Environment configuration
- Docker development environment
- Auth Service
- User registration
- Login
- Password hashing
- JWT
- Refresh token
- RBAC
- Redis integration
- Order domain
- Order entity
- Order items
- Order repository
- Create order
- Get orders
- Cancel order
- Order state machine
- Kafka Docker setup
- Kafka client library
- Producer
- Consumer
- Topics
- Consumer groups
- Event contracts
- Event versioning
- Outbox table
- Atomic DB + outbox transaction
- Outbox publisher
- Kafka publishing
- Retry failed events
- Publisher concurrency handling
- Published event tracking
- Payment domain
- Payment DB
- Payment creation
- Payment processing
- Payment success
- Payment failure
- Refund
- Idempotency
- Product model
- Inventory model
- Stock management
- Reservation
- Release
- Concurrency handling
- Inventory events
Implement:
Order Created
β
Payment
β
Inventory
β
Order Confirmation
Handle failures with compensating actions.
- Processed events
- Idempotent consumers
- Retry
- Exponential backoff
- DLQ
- Timeouts
- Circuit breaker
- Graceful shutdown
- Redis module
- Product cache
- Rate limiting
- BullMQ
- Workers
- Retry jobs
- Failed jobs
- Kafka consumers
- Notification domain
- Email queue
- Email worker
- WebSocket notifications
- Notification history
- Structured logging
- Correlation IDs
- Health checks
- Prometheus
- Grafana
- Kafka metrics
- Queue metrics
- Database metrics
- Unit tests
- Integration tests
- Kafka tests
- Repository tests
- E2E tests
- Failure scenario tests
- Load testing
- Dockerize all services
- Production Docker Compose
- Nginx
- TLS
- Secrets management
- Database backups
- Kafka persistence
- Monitoring
- Logging
- Graceful deployment
- CI/CD
The project will be considered complete when this workflow works reliably:
User
β
βββ Register
β
βββ Login
β
βββ Create Order
β
βΌ
API Gateway
β
βΌ
Order Service
β
βββ PostgreSQL Transaction
β βββ Order
β βββ Order Items
β βββ Outbox Event
β
βΌ
Kafka
β
ββββββ΄βββββ
βΌ βΌ
Payment Inventory
Service Service
β β
βΌ βΌ
Payment Reserve
Result Stock
β β
ββββββ¬βββββ
βΌ
Kafka
β
βΌ
Order Service
β
βΌ
Order Confirmed
β
βΌ
Notification Service
β
βΌ
BullMQ + Redis
β
βΌ
Worker
β
βΌ
Email / WebSocket
The system must also correctly handle:
Kafka failure
Database failure
Duplicate event
Consumer crash
Payment failure
Inventory failure
Notification failure
Worker failure
without corrupting business data.
This project follows these principles:
1. Each service owns its data.
2. Never share business databases between services.
3. Use Kafka for asynchronous domain events.
4. Use REST only when synchronous communication is actually required.
5. Use Transactional Outbox for reliable event publishing.
6. Consumers must be idempotent.
7. Expect failures.
8. Use retries for transient failures.
9. Use DLQ for unrecoverable events.
10. Business logic should not depend directly on infrastructure.
11. Keep API Gateway thin.
12. Prefer eventual consistency over distributed transactions.
13. Keep shared libraries truly generic.
14. Do not create abstractions without a real reason.
15. Optimize for correctness before performance.
Recommended order for learning this project:
NestJS
β
PostgreSQL + Prisma
β
Clean Architecture
β
SOLID
β
Microservices
β
Kafka Basics
β
Kafka Producer / Consumer
β
Event Design
β
Transactional Outbox
β
Idempotency
β
Eventual Consistency
β
Redis
β
BullMQ
β
Retry + DLQ
β
Observability
β
Docker
β
Production Deployment
This project is not intended to be a simple CRUD application.
The primary objective is to understand how a production backend behaves when:
multiple services
+
multiple databases
+
asynchronous events
+
network failures
+
duplicate messages
+
background processing
+
eventual consistency
are combined into one system.
The final result should demonstrate practical knowledge of NestJS microservices, Kafka, PostgreSQL transactions, Transactional Outbox, Redis, BullMQ, SOLID, Clean Architecture, and distributed-system reliability patterns.
This project is created for learning, experimentation, and portfolio purposes.