Microservices Architecture with Node.js: A Practical Guide
Microservices architecture has gained a lot of popularity in recent years, as it allows developers to break down complex applications into smaller, more manageable components. This approach can improve scalability, maintainability, and resiliency of an application. In this blog post, we will provide a practical guide on how to implement a microservices architecture using Node.js. We will walk you through the entire process, from understanding the basics of microservices and setting up the development environment, to creating and deploying a simple application using this architecture.
Introduction to Microservices
Microservices architecture is an approach to developing a software application as a suite of small, independent services that are built around specific business capabilities. These services are responsible for handling specific tasks and communicate with each other using APIs. This architecture provides several benefits, such as:
- Improved scalability: Since each service can be scaled independently, you can better allocate resources to the services that need them the most.
- Better maintainability: Smaller codebases are easier to understand and maintain, which helps to reduce the complexity of the application.
- Enhanced resiliency: If one service fails, it doesn't necessarily mean the whole system will fail. This allows for better fault isolation and faster recovery.
Setting Up the Development Environment
Before we begin, you need to have Node.js installed on your system. You can download the latest version of Node.js from the official website. Once you have installed Node.js, you can check its version by running the following command in your terminal:
node -v
To manage the dependencies of our microservices, we will use npm
(Node Package Manager), which comes bundled with Node.js. To check if npm
is installed, you can run:
npm -v
Next, create a new directory for your microservices project and navigate to it:
mkdir microservices-nodejs && cd microservices-nodejs
Creating a Basic Microservice
To create a basic microservice, we will use the Express.js framework. You can install it using npm
:
npm init -y npm install express
Now, create a new file named app.js
in your project directory and add the following code:
const express = require('express'); const app = express(); const port = process.env.PORT || 3000; app.get('/', (req, res) => { res.send('Hello from the Microservice!'); }); app.listen(port, () => { console.log(`Microservice listening on port ${port}`); });
This code sets up a basic Express server that listens for requests on port 3000 and responds with a greeting message. To start the server, run the following command:
node app.js
You should see the message "Microservice listening on port 3000" in your terminal. To test the microservice, you can visit http://localhost:3000
in your web browser or use a tool like Postman.
Structuring the Microservices Project
In a real-world scenario, you will have multiple microservices working together. To better organize our project, let's create a directory for each microservice. For this example, we will create two microservices: orders
and inventory
.
Create the following directory structure:
microservices-nodejs/
|- orders/
|- inventory/
Move the app.js
file and node_modules
directory into the orders
directory, and create a new app.js
file in the inventory
directory. Update the app.js
files in each directory to reflect the specificmicroservice:
orders/app.js:
const express = require('express'); const app = express(); const port = process.env.PORT || 3001; app.get('/orders', (req, res) => { res.send('Hello from the Orders Microservice!'); }); app.listen(port, () => { console.log(`Orders Microservice listening on port ${port}`); });
inventory/app.js:
const express = require('express'); const app = express(); const port = process.env.PORT || 3002; app.get('/inventory', (req, res) => { res.send('Hello from the Inventory Microservice!'); }); app.listen(port, () => { console.log(`Inventory Microservice listening on port ${port}`); });
Now, each microservice has its own server and listens on a different port. To start both microservices, open two terminals, navigate to each microservice directory, and run node app.js
.
Communication Between Microservices
In a microservices architecture, services often need to communicate with each other. One common approach is using RESTful APIs. To demonstrate this, let's create an endpoint in the orders
microservice that fetches data from the inventory
microservice.
First, install the axios
library in the orders
directory to make HTTP requests:
cd orders npm install axios
Next, update the orders/app.js
file to include the following code:
const axios = require('axios'); // ... app.get('/orders/inventory', async (req, res) => { try { const inventoryResponse = await axios.get('http://localhost:3002/inventory'); res.send(`Orders Microservice received data from Inventory Microservice: ${inventoryResponse.data}`); } catch (error) { console.error(`Error fetching data from Inventory Microservice: ${error.message}`); res.status(500).send('Error fetching data from Inventory Microservice'); } });
Now, when you make a request to http://localhost:3001/orders/inventory
, the orders
microservice will fetch data from the inventory
microservice and return it to the client.
In a microservices architecture, it's crucial to secure the communication between services. One way to achieve this is by using JSON Web Tokens (JWT). To demonstrate this, let's implement JWT-based authentication for our microservices.
First, install the jsonwebtoken
and express-jwt
libraries in both microservice directories:
cd ../inventory npm install jsonwebtoken express-jwt cd ../orders npm install jsonwebtoken express-jwt
Next, update the orders/app.js
and inventory/app.js
files to include the following code for generating and validating JWT tokens:
orders/app.js:
const jwt = require('jsonwebtoken'); const expressJwt = require('express-jwt'); // ... const jwtSecret = 'my-secret-key'; app.post('/orders/auth', (req, res) => { const token = jwt.sign({ user: 'orders-service' }, jwtSecret, { expiresIn: '1h' }); res.send({ token }); }); app.use(expressJwt({ secret: jwtSecret, algorithms: ['HS256'] }).unless({ path: ['/orders/auth'] })); // ...
inventory/app.js:
const jwt = require('jsonwebtoken'); const expressJwt = require('express-jwt'); // ... const jwtSecret = 'my-secret-key'; app.post('/inventory/auth', (req, res) => { const token = jwt.sign({ user: 'inventory-service' }, jwtSecret,{ expiresIn: '1h' }); res.send({ token }); }); app.use(expressJwt({ secret: jwtSecret, algorithms: ['HS256'] }).unless({ path: ['/inventory/auth'] })); // ...
Now, both microservices require a valid JWT token to access their endpoints (except for the /auth
endpoints). To test this, update the /orders/inventory
endpoint in the orders/app.js
file to include the JWT token when making a request to the inventory
microservice:
app.get('/orders/inventory', async (req, res) => { try { const token = req.headers.authorization; const inventoryResponse = await axios.get('http://localhost:3002/inventory', { headers: { authorization: token } }); res.send(`Orders Microservice received data from Inventory Microservice: ${inventoryResponse.data}`); } catch (error) { console.error(`Error fetching data from Inventory Microservice: ${error.message}`); res.status(500).send('Error fetching data from Inventory Microservice'); } });
To test the authentication, you can use a tool like Postman to obtain a JWT token from the /auth
endpoint of each microservice and then include it in the request headers when making requests to the other endpoints.
Deploying Microservices
For deploying microservices, containerization platforms like Docker and orchestration tools like Kubernetes are commonly used. In this guide, we'll briefly demonstrate how to create a Docker container for each microservice.
First, install Docker on your system if you haven't already. Next, create a Dockerfile
in both the orders
and inventory
directories with the following content:
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD [ "node", "app.js" ]
Make sure to replace EXPOSE 3000
with the correct port for each microservice (i.e., EXPOSE 3001
for orders
and EXPOSE 3002
for inventory
).
Now, you can build the Docker images for each microservice:
cd orders docker build -t orders-service . cd ../inventory docker build -t inventory-service .
And finally, you can run the Docker containers for each microservice:
docker run -p 3001:3001 -d orders-service docker run -p 3002:3002 -d inventory-service
Your microservices should now be running inside Docker containers, and you can access them as you did before.
Conclusion
In this blog post, we've provided a practical guide for implementing a microservices architecture using Node.js. We've covered the basics of microservices, setting up the development environment, creating and deploying a simple application using this architecture, and securing communication between services with JWT-based authentication.
By following this guide, you should now have a better understanding of how to create and manage a microservices-based application using Node.js. Remember that this is just the beginning – as your application grows, you'll need to consider more advanced topics like service discovery, load balancing, and monitoring to ensure your microservices are efficient, reliable, and easy to maintain.
Sharing is caring
Did you like what Mehul Mohan wrote? Thank them for their work by sharing it on social media.
No comments so far
Curious about this topic? Continue your journey with these coding courses: