Complete Guide to Building Microservices with Node.js and Docker

A
Admin
January 9, 2026 • 8 min read
Complete Guide to Building Microservices with Node.js and Docker

1.Introduction to Microservices Architecture

Microservices architecture has revolutionized how we build and deploy modern applications. Instead of monolithic applications, we break down functionality into smaller, independent services that communicate through well-defined APIs. This guide will walk you through building a complete microservices ecosystem using Node.js and Docker.

2.Why Microservices?

Before diving into implementation, let's understand the benefits and challenges of microservices:

Aspect Monolithic Microservices
Deployment Single deployment unit Independent service deployment
Scaling Scale entire application Scale individual services
Technology Stack Single stack Polyglot architecture
Development Tight coupling Loose coupling
Fault Isolation Single point of failure Isolated failures

3.Project Structure

A well-organized microservices project follows this structure:

microservices-app/ ├── services/ │ ├── auth-service/ │ │ ├── src/ │ │ ├── Dockerfile │ │ └── package.json │ ├── user-service/ │ │ ├── src/ │ │ ├── Dockerfile │ │ └── package.json │ └── product-service/ │ ├── src/ │ ├── Dockerfile │ └── package.json ├── api-gateway/ │ ├── src/ │ ├── Dockerfile │ └── package.json ├── docker-compose.yml └── README.md

4.Building the Authentication Service

Let's start with a critical service - authentication. This service handles user registration, login, and JWT token generation.

Setting Up the Project

mkdir auth-service && cd auth-service npm init -y npm install express jsonwebtoken bcryptjs dotenv cors helmet npm install --save-dev nodemon

Authentication Service Code

// auth-service/src/index.js const express = require('express'); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const helmet = require('helmet'); const cors = require('cors'); const app = express(); const PORT = process.env.PORT || 3001; const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; // Middleware app.use(helmet()); app.use(cors()); app.use(express.json()); // In-memory user store (use database in production) const users = new Map(); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'healthy', service: 'auth-service' }); }); // Register endpoint app.post('/api/auth/register', async (req, res) => { try { const { username, email, password } = req.body; // Validation if (!username || !email || !password) { return res.status(400).json({ error: 'Missing required fields' }); } // Check if user exists if (users.has(email)) { return res.status(409).json({ error: 'User already exists' }); } // Hash password const hashedPassword = await bcrypt.hash(password, 10); // Store user const user = { id: Date.now().toString(), username, email, password: hashedPassword, createdAt: new Date() }; users.set(email, user); // Generate token const token = jwt.sign( { id: user.id, email: user.email }, JWT_SECRET, { expiresIn: '24h' } ); res.status(201).json({ message: 'User registered successfully', token, user: { id: user.id, username: user.username, email: user.email } }); } catch (error) { console.error('Registration error:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Login endpoint app.post('/api/auth/login', async (req, res) => { try { const { email, password } = req.body; // Validation if (!email || !password) { return res.status(400).json({ error: 'Missing credentials' }); } // Find user const user = users.get(email); if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } // Verify password const isValid = await bcrypt.compare(password, user.password); if (!isValid) { return res.status(401).json({ error: 'Invalid credentials' }); } // Generate token const token = jwt.sign( { id: user.id, email: user.email }, JWT_SECRET, { expiresIn: '24h' } ); res.json({ message: 'Login successful', token, user: { id: user.id, username: user.username, email: user.email } }); } catch (error) { console.error('Login error:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Verify token endpoint app.post('/api/auth/verify', (req, res) => { try { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'No token provided' }); } const decoded = jwt.verify(token, JWT_SECRET); res.json({ valid: true, user: decoded }); } catch (error) { res.status(401).json({ valid: false, error: 'Invalid token' }); } }); app.listen(PORT, () => { console.log(`Auth service running on port ${PORT}`); });

Dockerfile for Auth Service

# auth-service/Dockerfile FROM node:18-alpine WORKDIR /app # Copy package files COPY package*.json ./ # Install dependencies RUN npm ci --only=production # Copy source code COPY src ./src # Expose port EXPOSE 3001 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD node -e "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" # Start service CMD ["node", "src/index.js"]

5.Building the User Service

The user service manages user profiles and preferences.

// user-service/src/index.js const express = require('express'); const helmet = require('helmet'); const cors = require('cors'); const axios = require('axios'); const app = express(); const PORT = process.env.PORT || 3002; const AUTH_SERVICE_URL = process.env.AUTH_SERVICE_URL || 'http://auth-service:3001'; app.use(helmet()); app.use(cors()); app.use(express.json()); // Middleware to verify JWT const authenticateToken = async (req, res, next) => { try { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'Authentication required' }); } // Verify token with auth service const response = await axios.post( `${AUTH_SERVICE_URL}/api/auth/verify`, {}, { headers: { authorization: `Bearer ${token}` } } ); if (response.data.valid) { req.user = response.data.user; next(); } else { res.status(401).json({ error: 'Invalid token' }); } } catch (error) { res.status(401).json({ error: 'Authentication failed' }); } }; // In-memory user profiles const profiles = new Map(); // Get user profile app.get('/api/users/profile', authenticateToken, (req, res) => { const profile = profiles.get(req.user.id) || { id: req.user.id, email: req.user.email, bio: '', avatar: '', preferences: {} }; res.json(profile); }); // Update user profile app.put('/api/users/profile', authenticateToken, (req, res) => { const { bio, avatar, preferences } = req.body; const profile = { id: req.user.id, email: req.user.email, bio: bio || '', avatar: avatar || '', preferences: preferences || {}, updatedAt: new Date() }; profiles.set(req.user.id, profile); res.json(profile); }); app.listen(PORT, () => { console.log(`User service running on port ${PORT}`); });

6.API Gateway with Express

The API Gateway acts as a single entry point for all client requests, routing them to appropriate microservices.

// api-gateway/src/index.js const express = require('express'); const { createProxyMiddleware } = require('http-proxy-middleware'); const helmet = require('helmet'); const cors = require('cors'); const rateLimit = require('express-rate-limit'); const app = express(); const PORT = process.env.PORT || 3000; // Security middleware app.use(helmet()); app.use(cors()); app.use(express.json()); // Rate limiting const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100 // limit each IP to 100 requests per windowMs }); app.use('/api/', limiter); // Service URLs const services = { auth: process.env.AUTH_SERVICE_URL || 'http://auth-service:3001', user: process.env.USER_SERVICE_URL || 'http://user-service:3002', product: process.env.PRODUCT_SERVICE_URL || 'http://product-service:3003' }; // Route to auth service app.use('/api/auth', createProxyMiddleware({ target: services.auth, changeOrigin: true, pathRewrite: { '^/api/auth': '/api/auth' } })); // Route to user service app.use('/api/users', createProxyMiddleware({ target: services.user, changeOrigin: true, pathRewrite: { '^/api/users': '/api/users' } })); // Route to product service app.use('/api/products', createProxyMiddleware({ target: services.product, changeOrigin: true, pathRewrite: { '^/api/products': '/api/products' } })); // Health check app.get('/health', (req, res) => { res.json({ status: 'healthy', service: 'api-gateway' }); }); app.listen(PORT, () => { console.log(`API Gateway running on port ${PORT}`); });

7.Docker Compose Configuration

Docker Compose orchestrates all microservices together:

# docker-compose.yml version: '3.8' services: # API Gateway api-gateway: build: ./api-gateway ports: - "3000:3000" environment: - AUTH_SERVICE_URL=http://auth-service:3001 - USER_SERVICE_URL=http://user-service:3002 - PRODUCT_SERVICE_URL=http://product-service:3003 depends_on: - auth-service - user-service - product-service networks: - microservices-network # Auth Service auth-service: build: ./services/auth-service ports: - "3001:3001" environment: - PORT=3001 - JWT_SECRET=your-super-secret-jwt-key networks: - microservices-network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3001/health"] interval: 30s timeout: 10s retries: 3 # User Service user-service: build: ./services/user-service ports: - "3002:3002" environment: - PORT=3002 - AUTH_SERVICE_URL=http://auth-service:3001 depends_on: - auth-service networks: - microservices-network # Product Service product-service: build: ./services/product-service ports: - "3003:3003" environment: - PORT=3003 - AUTH_SERVICE_URL=http://auth-service:3001 depends_on: - auth-service networks: - microservices-network # Redis for caching redis: image: redis:7-alpine ports: - "6379:6379" networks: - microservices-network # PostgreSQL database postgres: image: postgres:15-alpine environment: - POSTGRES_USER=admin - POSTGRES_PASSWORD=password - POSTGRES_DB=microservices ports: - "5432:5432" volumes: - postgres-data:/var/lib/postgresql/data networks: - microservices-network networks: microservices-network: driver: bridge volumes: postgres-data:

8.Running the Microservices

Deploy your microservices ecosystem with these commands:

# Build all services docker-compose build # Start all services docker-compose up -d # View logs docker-compose logs -f # Check service health docker-compose ps # Stop all services docker-compose down

9.Testing the Microservices

Test your microservices with these API calls:

# Register a new user curl -X POST http://localhost:3000/api/auth/register \ -H "Content-Type: application/json" \ -d '{ "username": "john_doe", "email": "john@example.com", "password": "SecurePass123!" }' # Login curl -X POST http://localhost:3000/api/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "john@example.com", "password": "SecurePass123!" }' # Get user profile (use token from login) curl -X GET http://localhost:3000/api/users/profile \ -H "Authorization: Bearer YOUR_JWT_TOKEN"

10.Monitoring and Logging

Implement centralized logging with Winston and monitoring with Prometheus:

Tool Purpose Integration
Winston Structured logging npm install winston
Prometheus Metrics collection prom-client library
Grafana Visualization Docker container
Jaeger Distributed tracing OpenTelemetry

11.Best Practices

Follow these best practices for production-ready microservices:

  • Service Discovery: Use Consul or Eureka for dynamic service registration
  • Circuit Breakers: Implement with libraries like Opossum to prevent cascade failures
  • API Versioning: Version your APIs (e.g., /api/v1/users) for backward compatibility
  • Database per Service: Each microservice should have its own database
  • Event-Driven Communication: Use message queues (RabbitMQ, Kafka) for async communication
  • Security: Implement OAuth2, rate limiting, and input validation
  • Documentation: Use Swagger/OpenAPI for API documentation
  • Testing: Write unit, integration, and end-to-end tests

12.Scaling Strategies

Scale your microservices effectively:

# Scale specific service to 3 instances docker-compose up -d --scale user-service=3 # With Kubernetes kubectl scale deployment user-service --replicas=5

13.Conclusion

Building microservices with Node.js and Docker provides a robust, scalable architecture for modern applications. This guide covered the fundamentals, from service creation to deployment and monitoring. Start small, iterate, and gradually expand your microservices ecosystem as your application grows.

Remember: microservices add complexity, so only adopt them when your application truly needs the scalability and flexibility they provide.

Comments (0)

Leave a Comment

Loading comments...