Introduction: Why Microservices in JavaScript?
\nModern web development is increasingly moving away from monolithic applications in favor of a microservices architecture. JavaScript, or more precisely its server-side environment Node.js, is ideally suited for building microservices thanks to its asynchronicity, event-driven model, and vast ecosystem community. In this article, we will break down how to design, develop, and deploy microservices in JavaScript — from theory to practical code examples.
\n\n1. Fundamentals of Microservices Architecture
\nMicroservices architecture is an approach where an application is divided into small, independent services. Each service is responsible for its own business logic, has its own data storage, and can be deployed, scaled, and updated separately.
\nAdvantages of Microservices on Node.js
\n- \n
- Scalability: Node.js easily handles thousands of concurrent connections thanks to non-blocking I/O. \n
- Rapid Prototyping: npm packages and frameworks (Express, Fastify, NestJS) allow for quickly creating REST/GraphQL APIs. \n
- Error Isolation: A failure in one service does not paralyze the entire application. \n
- Technological Flexibility: Different services can use different versions of Node.js or even different languages. \n
Key Components
\n- \n
- API Gateway — a single entry point that routes requests to the appropriate services. \n
- Message Broker (RabbitMQ, Redis, Kafka) — for asynchronous data exchange. \n
- Service Registry (Consul, etcd) — for service discovery. \n
- Containerization (Docker, Kubernetes) — for standardizing the environment. \n
2. Practice: Creating Two Microservices
\nLet's examine an example of an order system. We will have two microservices: user-service (user management) and order-service (order management). They will communicate via REST API and asynchronously via RabbitMQ.
2.1. Basic Service on Express
\nLet's start with user-service. Install the dependencies:
npm init -y\nnpm install express body-parser\nCreate the file index.js:
const express = require('express');\nconst bodyParser = require('body-parser');\nconst app = express();\nconst PORT = process.env.PORT || 3001;\n\napp.use(bodyParser.json());\n\nconst users = [];\n\napp.post('/users', (req, res) => {\n const { name, email } = req.body;\n const user = { id: users.length + 1, name, email };\n users.push(user);\n res.status(201).json(user);\n});\n\napp.get('/users/:id', (req, res) => {\n const user = users.find(u => u.id === parseInt(req.params.id));\n if (!user) return res.status(404).json({ error: 'User not found' });\n res.json(user);\n});\n\napp.listen(PORT, () => {\n console.log(`User service running on port ${PORT}`);\n});\nSimilarly, create order-service on port 3002. It will contain an array of orders and two endpoints: POST /orders and GET /orders/:id.
2.2. API Gateway
\nLet's create a simple gateway on port 3000 that redirects requests based on the path:
\nconst express = require('express');\nconst httpProxy = require('http-proxy-middleware');\nconst app = express();\n\napp.use('/users', httpProxy.createProxyMiddleware({ target: 'http://localhost:3001', changeOrigin: true }));\napp.use('/orders', httpProxy.createProxyMiddleware({ target: 'http://localhost:3002', changeOrigin: true }));\n\napp.listen(3000, () => console.log('API Gateway on port 3000'));\nNow all requests to /users go to user-service, and to /orders go to order-service.
2.3. Asynchronous Interaction with RabbitMQ
\nSuppose that when creating an o