Node.js microservices example implementing a simple Task App using MongoDB, RabbitMQ and Docker.
This repository contains a small set of microservices that together implement a Task management application.
- Auth service manages credentials and issues JWTs.
- User service manages user profile data.
- Task service manages tasks and publishes task events to RabbitMQ.
- Notification service consumes RabbitMQ events.
Services are containerized with Docker Compose, and can also be run locally.
- Platform: Node.js microservices
- Frameworks/Libraries: Express, Mongoose
- Database: MongoDB
- Broker: RabbitMQ
- Containers: Docker & Docker Compose
- User registration/login using JWT
- User profile CRUD (separate from auth data)
- Task creation and list
- Task events published to RabbitMQ (consumed by notification-service)
auth-service/— authentication API (JWT + users collection)user-service/— user profile APItask-service/— task CRUD + RabbitMQ publishernotification-service/— RabbitMQ consumer (notifications)docker-compose.yml— orchestration for all services, MongoDB and RabbitMQreadme/design-auth.svg— architecture diagram
- VS Code:
- Install an extension that supports Mermaid rendering/export (for example, "Markdown Preview Mermaid Support" or a Mermaid exporter).
- Export the Mermaid block as an image.
- GitHub:
- GitHub renders Mermaid in Markdown; you can take a screenshot or use a Mermaid export tool.
If you want, tell me whether you prefer PNG or SVG, and I’ll generate a text-based diagram file (SVG) under readme/ that you can commit and render anywhere.
- Auth Service:
3004 - User Service:
3001 - Task Service:
3002 - Notification Service:
3003 - RabbitMQ Management UI:
15672 - MongoDB:
27017
- Ensure Docker and Docker Compose are installed.
- From the repository root, bring up all services:
docker-compose up -d --build- Watch logs for a service (example):
docker-compose logs -f auth-serviceYou can still use MongoDB/RabbitMQ from Docker and run a service on your machine.
Example:
cd auth-service
npm install
npm run devAt repo root there is a shared .env file used by Docker Compose.
NODE_ENV=development
JWT_SECRET=your_secure_jwt_secret_key_here
JWT_EXPIRES_IN=1d
AUTH_SERVICE_PORT=3004
USER_SERVICE_PORT=3001
TASK_SERVICE_PORT=3002
NOTIFICATION_SERVICE_PORT=3003
MONGODB_URI=mongodb://mongo:27017/
RABBITMQ_URL=amqp://rabbitmq
AUTH_SERVICE_URL=http://localhost:3004
USER_SERVICE_URL=http://localhost:3001
TASK_SERVICE_URL=http://localhost:3002
NOTIFICATION_SERVICE_URL=http://localhost:3003- Inside Docker Compose, services should call each other via service names:
- Example:
http://user-service:3001
- Example:
- From your browser / Postman / curl on your machine, use
http://localhost:<port>.
In docker-compose.yml:
auth-serviceusesUSER_SERVICE_URL=http://user-service:3001(container-to-container).- Some services may also have
AUTH_SERVICE_URL=http://localhost:3004(this only works if the container can reach your host network, and is not recommended for container-to-container calls).
This project intentionally separates authentication data and profile data:
- Auth DB (
auth) →userscollection- Stores:
email,username,password (hashed),role
- Stores:
- Users DB (
users) →userprofilescollection- Stores:
userId(links to auth user_id),name,email, profile fields (bio,avatar,preferences)
- Stores:
The link is the userId stored in userprofiles.
Base URLs (local):
- Auth:
http://localhost:3004 - Users:
http://localhost:3001 - Tasks:
http://localhost:3002
POST /api/auth/register
Creates a new auth user and then creates a matching user profile in user-service.
Request body:
{
"name": "John Doe",
"email": "john.doe@example.com",
"username": "johndoe",
"password": "SecurePassword123!"
}Response 201:
{
"user": {
"_id": "<mongo_object_id>",
"email": "john.doe@example.com",
"username": "johndoe",
"role": "user",
"createdAt": "2025-12-30T00:00:00.000Z",
"updatedAt": "2025-12-30T00:00:00.000Z"
},
"token": "<jwt>"
}Notes:
- First registered user becomes
admin(see auth-service logic). - Password is never returned.
curl:
curl -X POST "http://localhost:3004/api/auth/register" \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"email": "john.doe@example.com",
"username": "johndoe",
"password": "SecurePassword123!"
}'POST /api/auth/login
Request body:
{
"username": "johndoe",
"password": "SecurePassword123!"
}Notes:
usernamefield accepts username or email.
Response 200:
{
"user": {
"_id": "<mongo_object_id>",
"email": "john.doe@example.com",
"username": "johndoe",
"role": "user",
"id": "<profile_id>",
"userId": "<mongo_object_id>",
"name": "John Doe",
"bio": "",
"avatar": "",
"preferences": {
"theme": "light",
"notifications": {
"email": true,
"push": true
}
},
"createdAt": "2025-12-30T00:00:00.000Z",
"updatedAt": "2025-12-30T00:00:00.000Z"
},
"token": "<jwt>"
}curl:
curl -X POST "http://localhost:3004/api/auth/login" \
-H "Content-Type: application/json" \
-d '{
"username": "johndoe",
"password": "SecurePassword123!"
}'GET /api/auth/me
Headers:
Authorization: Bearer <jwt>
Response 200 combines auth user + user profile:
{
"_id": "<mongo_object_id>",
"email": "john.doe@example.com",
"username": "johndoe",
"role": "user",
"id": "<profile_id>",
"userId": "<mongo_object_id>",
"name": "John Doe",
"bio": "",
"avatar": "",
"preferences": {
"theme": "light",
"notifications": {
"email": true,
"push": true
}
},
"createdAt": "2025-12-30T00:00:00.000Z",
"updatedAt": "2025-12-30T00:00:00.000Z"
}curl:
curl "http://localhost:3004/api/auth/me" \
-H "Authorization: Bearer <jwt>"There is no server-side logout endpoint.
This project uses stateless JWT, so logout is handled by the client by deleting the token.
All user-service endpoints require:
Authorization: Bearer <jwt>
GET /api/users/me
Response 200:
{
"id": "<profile_id>",
"userId": "<auth_user_id>",
"name": "John Doe",
"email": "john.doe@example.com",
"bio": "",
"avatar": "",
"preferences": {
"theme": "light",
"notifications": {
"email": true,
"push": true
}
},
"createdAt": "2025-12-30T00:00:00.000Z",
"updatedAt": "2025-12-30T00:00:00.000Z"
}curl:
curl "http://localhost:3001/api/users/me" \
-H "Authorization: Bearer <jwt>"PATCH /api/users/me
Request body (example):
{
"bio": "Senior developer",
"preferences": {
"theme": "dark"
}
}curl:
curl -X PATCH "http://localhost:3001/api/users/me" \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{
"bio": "Senior developer",
"preferences": {"theme": "dark"}
}'GET /api/users?page=1&limit=10
Requirements:
- JWT must include
role: "admin".
Response 200:
{
"data": [
{
"id": "<profile_id>",
"userId": "<auth_user_id>",
"name": "John Doe",
"email": "john.doe@example.com",
"bio": "",
"avatar": "",
"preferences": {
"theme": "light",
"notifications": {
"email": true,
"push": true
}
},
"createdAt": "2025-12-30T00:00:00.000Z",
"updatedAt": "2025-12-30T00:00:00.000Z"
}
],
"pagination": {
"total": 1,
"page": 1,
"limit": 10,
"totalPages": 1
}
}If you get 403 Not authorized, log in with an admin token (first registered user is admin).
Task service currently exposes simple endpoints:
GET /tasks
curl:
curl "http://localhost:3002/tasks"POST /tasks
Request body:
{
"title": "Finish report",
"description": "Complete the quarterly report",
"userId": "<auth_user_id>"
}Notes:
- On create, a message is published to RabbitMQ queue
task_created.
curl:
curl -X POST "http://localhost:3002/tasks" \
-H "Content-Type: application/json" \
-d '{
"title": "Finish report",
"description": "Complete the quarterly report",
"userId": "<auth_user_id>"
}'Notification service is intended to consume RabbitMQ messages (e.g., task_created).
{
"errors": [
{
"type": "field",
"msg": "Password must be at least 8 characters",
"path": "password",
"location": "body"
}
]
}{
"message": "No token provided"
}{
"message": "Not authorized to access this resource"
}If tests exist for a service:
cd user-service
npm test- Add rate limiting, CORS policy, and centralized logging for production.
- Consider adding refresh tokens and a token denylist if you need server-side logout.
- Consider improving inter-service communication so containers never depend on
localhostURLs.