A production-ready, event-driven microservices backend built with Node.js, Express, MongoDB, RabbitMQ, and Docker β featuring an API Gateway, asynchronous notifications, service discovery via Docker DNS, and full container orchestration.
- Overview
- Architecture
- Services
- Tech Stack
- Project Structure
- Getting Started
- API Reference
- Event Flow
- Design Decisions
TaskFlow is a distributed, microservice-based backend system that handles user management, task lifecycle, and asynchronous email notifications. Each service is independently deployable, owns its own data domain, and communicates either via HTTP (synchronous) or RabbitMQ (asynchronous) depending on the use case.
All client requests flow through a single API Gateway β which handles routing, CORS, and rate limiting β before reaching the appropriate service.
When a task is created, the system:
- Client hits the API Gateway (
localhost:8080) - Gateway forwards the request to
task-service - Task is persisted in MongoDB
- A
TASK_CREATEDevent is published to RabbitMQ notification-serviceasynchronously notifies the assigned user via email β completely decoupled from the main task flow
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CLIENT / FRONTEND β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββ
β HTTP Request
βΌ
ββββββββββββββββββββββββββββββββββββββββ
β api-gateway :8080 β
β β’ Single entry point for client β
β β’ CORS, Rate Limiting, Routing β
ββββββββββββββ¬ββββββββββββββββββββββββββ
β
βββββββββ΄βββββββββ
β β
βΌ βΌ
βββββββββββββββββββ ββββββββββββββββββββββββββββββββ
β user-service β β task-service :3002 β
β :3001 β β β’ Validates & stores task β
β β’ Manages users β β β’ Publishes TASK_CREATED β
ββββββββββ¬βββββββββ ββββββββββββ¬ββββββββββββ¬βββββββββ
β β β
MongoDBβ MongoDBβ β RabbitMQ Publish
(users)β (tasks)β β
βΌ βΌ βΌ
ββββββββββββββ βββββββββββββββ ββββββββββββββββββββ
β MongoDB β β MongoDB β β RabbitMQ β
β (users DB) β β (tasks DB) β β task_created β
ββββββββββββββ βββββββββββββββ ββββββββββ¬ββββββββββ
β Consume
βΌ
βββββββββββββββββββββββββββββ
β notification-service β
β :3003 β
β β’ Consumes TASK_CREATED β
β β’ Calls user-service β
β β’ Sends email via SMTP β
βββββββββββββββββββββββββββββ
The single entry point for all client requests. No business logic, no database β pure infrastructure.
- Routes
/api/users/*βuser-service:3001 - Routes
/api/tasks/*βtask-service:3002 - Handles CORS so the React client can communicate freely
- Enforces rate limiting (100 requests / 15 min per IP)
- Logs every request via
morgan
Manages user accounts and exposes REST APIs to create and retrieve user data. Acts as the source of truth for user identity β other services query it over the internal Docker network.
- Stores users in the
usersMongoDB database - Exposes
GET /users/:idused internally bynotification-service - Exposes
GET /usersandPOST /usersfor user management
Handles task creation and persistence. After storing a task, it publishes a TASK_CREATED event to RabbitMQ to trigger downstream processing without blocking the HTTP response.
- Stores tasks in the
tasksMongoDB database - Publishes messages to the
task_createdqueue viaamqplib - Implements retry logic for RabbitMQ connection initialization
Runs as an event consumer β it continuously listens to the task_created RabbitMQ queue. On receiving a message, it:
- Extracts
userIdfrom the message payload - Calls
user-serviceover Docker's internal network (http://user-service:3001/...) - Sends an email notification (currently simulated via console logging for development/testing purposes)
- Acknowledges (
ack) the message on success
Failures in this service do not affect task creation β total decoupling via async messaging.
| Layer | Technology |
|---|---|
| Runtime | Node.js |
| Framework | Express.js |
| Database | MongoDB (per-service logical DB) |
| Message Broker | RabbitMQ (amqplib) |
| Gateway | express-http-proxy, express-rate-limit, cors, morgan |
| Containerization | Docker + Docker Compose |
| Service Discovery | Docker DNS (internal hostnames) |
| Inter-service HTTP | Native fetch API |
- Docker installed
git clone https://github.com/your-username/taskflow.git
cd taskflowdocker compose up --buildThis spins up:
mongoβ MongoDB instancerabbitmqβ RabbitMQ broker (management UI athttp://localhost:15672)api-gatewayon port8080β start here for all requestsuser-serviceon port3001task-serviceon port3002notification-serviceon port3003
docker compose ps# All services
docker compose logs -f
# Specific service
docker compose logs -f api-gatewayAll requests go through the API Gateway at
http://localhost:8080
POST /api/users
Content-Type: application/json
{
"name": "Alice Johnson",
"email": "alice@example.com"
}Response:
{
"_id": "64f1a2b3c4d5e6f7a8b9c0d1",
"name": "Alice Johnson",
"email": "alice@example.com"
}GET /api/usersGET /api/users/:idResponse:
{
"_id": "64f1a2b3c4d5e6f7a8b9c0d1",
"name": "Alice Johnson",
"email": "alice@example.com"
}POST /api/tasks
Content-Type: application/json
{
"title": "Review pull request",
"description": "Review and merge the authentication PR",
"userId": "64f1a2b3c4d5e6f7a8b9c0d1"
}Response:
{
"_id": "74a2b3c4d5e6f7a8b9c0d1e2",
"title": "Review pull request",
"description": "Review and merge the authentication PR",
"userId": "64f1a2b3c4d5e6f7a8b9c0d1",
"createdAt": "2024-09-01T10:30:00.000Z"
}After a successful response, a
TASK_CREATEDevent is published to RabbitMQ and the user will receive an email notification asynchronously.
GET /api/tasksClient
β
β POST /api/tasks
βΌ
api-gateway :8080
β (rate limit check β route match β forward)
βΌ
task-service :3002
βββ Saves task to MongoDB (tasks DB)
βββ Publishes to RabbitMQ βββΊ queue: task_created
β
β (async)
βΌ
notification-service
βββ Consumes message
βββ Extracts userId
βββ GET http://user-service:3001/users/:userId
β β
β βΌ
β user-service
β βββ Returns { email, name }
β
βββ Sends email (Simulation)
βββ ack() β marks message as processed
{
"event": "TASK_CREATED",
"taskId": "74a2b3c4d5e6f7a8b9c0d1e2",
"userId": "64f1a2b3c4d5e6f7a8b9c0d1",
"title": "Review pull request",
"createdAt": "2024-09-01T10:30:00.000Z"
}The gateway decouples the client from individual service ports. The React frontend only ever talks to localhost:8080 β it has no knowledge of internal service URLs. This also centralizes cross-cutting concerns like CORS, rate limiting, and logging in one place.
MongoDB is shared at the infrastructure level across services, but each service connects to its own logical database (users, tasks). This maintains domain boundaries without the operational overhead of separate MongoDB instances.
Task creation and email notification are intentionally decoupled. A notification failure (e.g., SMTP timeout) has zero impact on the task creation response. This improves reliability and user experience.
Services implement retry logic when connecting to RabbitMQ at startup β essential since RabbitMQ may not be ready immediately when containers start.
Internal services resolve each other using Docker Compose service names (e.g., http://user-service:3001). No hardcoded IPs β Docker's internal DNS handles routing automatically.
The notification-service only calls ack() after successfully processing a message. If processing fails, the message stays unacknowledged and can be requeued β preventing silent data loss.
services:
mongo: # Shared MongoDB instance
rabbitmq: # Message broker (management UI: :15672)
api-gateway: # Single entry point β port 8080
user-service: # User management β port 3001
task-service: # Task CRUD + event publisher β port 3002
notification-service: # Event consumer + notification sender β port 3003All services share a custom Docker bridge network, enabling hostname-based service discovery.
Once running, visit http://localhost:15672
| Field | Value |
|---|---|
| Username | guest |
| Password | guest |
Built with β€οΈ using Node.js Β· Express Β· MongoDB Β· RabbitMQ Β· Docker