Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Express.js and Microservices

Microservices architecture involves developing applications as a collection of small, loosely coupled services. This guide covers key concepts, examples, and best practices for building microservices with Express.js.

Key Concepts of Microservices

  • Service Independence: Each service is developed, deployed, and scaled independently.
  • Communication: Services communicate with each other using lightweight protocols like HTTP or messaging queues.
  • Data Management: Each service manages its own database or data storage.
  • Decentralized Governance: Teams have the freedom to choose the best tools and technologies for their services.
  • Fault Isolation: Failures in one service do not affect the entire system.

Setting Up a Basic Microservice

Build a basic microservice with Express.js:

Example: Basic User Service

// Install necessary packages
// npm install express body-parser

// user-service/server.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3001;

app.use(bodyParser.json());

let users = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Smith' }
];

app.get('/users', (req, res) => {
    res.json(users);
});

app.post('/users', (req, res) => {
    const user = { id: users.length + 1, ...req.body };
    users.push(user);
    res.status(201).json(user);
});

app.listen(port, () => {
    console.log(`User service running at http://localhost:${port}/`);
});

Setting Up Communication Between Microservices

Enable communication between microservices using HTTP requests:

Example: Order Service Communicating with User Service

// Install necessary packages
// npm install express axios

// order-service/server.js
const express = require('express');
const axios = require('axios');
const app = express();
const port = 3002;

app.use(express.json());

let orders = [
    { id: 1, userId: 1, product: 'Laptop', quantity: 1 },
    { id: 2, userId: 2, product: 'Phone', quantity: 2 }
];

app.get('/orders', (req, res) => {
    res.json(orders);
});

app.post('/orders', async (req, res) => {
    const order = { id: orders.length + 1, ...req.body };
    const userId = req.body.userId;
    try {
        const userResponse = await axios.get(`http://localhost:3001/users/${userId}`);
        if (userResponse.data) {
            orders.push(order);
            res.status(201).json(order);
        } else {
            res.status(404).json({ message: 'User not found' });
        }
    } catch (error) {
        res.status(500).json({ message: 'Error fetching user data' });
    }
});

app.listen(port, () => {
    console.log(`Order service running at http://localhost:${port}/`);
});

Managing Data with Different Microservices

Each microservice manages its own data storage to maintain independence:

Example: Separate Databases for User and Order Services

// user-service/server.js (additional code)
const { Pool } = require('pg');
const pool = new Pool({
    user: 'postgres',
    host: 'localhost',
    database: 'userdb',
    password: 'password',
    port: 5432
});

app.get('/users', async (req, res) => {
    try {
        const result = await pool.query('SELECT * FROM users');
        res.json(result.rows);
    } catch (error) {
        res.status(500).json({ message: 'Error fetching users' });
    }
});

app.post('/users', async (req, res) => {
    const { name } = req.body;
    try {
        const result = await pool.query('INSERT INTO users (name) VALUES ($1) RETURNING *', [name]);
        res.status(201).json(result.rows[0]);
    } catch (error) {
        res.status(500).json({ message: 'Error creating user' });
    }
});

// order-service/server.js (additional code)
const { Pool } = require('pg');
const pool = new Pool({
    user: 'postgres',
    host: 'localhost',
    database: 'orderdb',
    password: 'password',
    port: 5432
});

app.get('/orders', async (req, res) => {
    try {
        const result = await pool.query('SELECT * FROM orders');
        res.json(result.rows);
    } catch (error) {
        res.status(500).json({ message: 'Error fetching orders' });
    }
});

app.post('/orders', async (req, res) => {
    const { userId, product, quantity } = req.body;
    try {
        const userResponse = await axios.get(`http://localhost:3001/users/${userId}`);
        if (userResponse.data) {
            const result = await pool.query('INSERT INTO orders (userId, product, quantity) VALUES ($1, $2, $3) RETURNING *', [userId, product, quantity]);
            res.status(201).json(result.rows[0]);
        } else {
            res.status(404).json({ message: 'User not found' });
        }
    } catch (error) {
        res.status(500).json({ message: 'Error creating order' });
    }
});

Using Docker for Containerization

Use Docker to containerize your microservices for consistent deployment:

Example: Dockerizing User Service

// user-service/Dockerfile
FROM node:14

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3001

CMD ["node", "server.js"]

// Build and run the Docker container
// docker build -t user-service .
// docker run -p 3001:3001 user-service

Example: Dockerizing Order Service

// order-service/Dockerfile
FROM node:14

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3002

CMD ["node", "server.js"]

// Build and run the Docker container
// docker build -t order-service .
// docker run -p 3002:3002 order-service

Best Practices for Microservices

  • Service Independence: Ensure each service can be developed, deployed, and scaled independently.
  • Use Lightweight Communication: Use lightweight communication protocols like HTTP or messaging queues.
  • Separate Data Storage: Each service should manage its own database or data storage.
  • Implement Fault Isolation: Ensure failures in one service do not affect the entire system.
  • Use Docker for Consistent Deployment: Containerize your microservices using Docker for consistent and reliable deployment.

Testing Microservices

Test your microservices to ensure they work as expected:

Example: Testing with Mocha

// Install Mocha and Chai
// npm install --save-dev mocha chai

// user-service/test/user.test.js
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const app = require('../server');

describe('User Service', () => {
    it('should get all users', (done) => {
        request(app)
            .get('/users')
            .expect(200)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.body).to.be.an('array');
                done();
            });
    });

    it('should create a new user', (done) => {
        request(app)
            .post('/users')
            .send({ name: 'Alice' })
            .expect(201)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.body).to.have.property('id');
                expect(res.body).to.have.property('name', 'Alice');
                done();
            });
    });
});

// Define test script in package.json
// "scripts": {
//   "test": "mocha"
// }

// Run tests with NPM
// npm run test

Key Points

  • Service Independence: Each service is developed, deployed, and scaled independently.
  • Communication: Services communicate with each other using lightweight protocols like HTTP or messaging queues.
  • Data Management: Each service manages its own database or data storage.
  • Decentralized Governance: Teams have the freedom to choose the best tools and technologies for their services.
  • Fault Isolation: Failures in one service do not affect the entire system.
  • Follow best practices for microservices, such as ensuring service independence, using lightweight communication, separating data storage, implementing fault isolation, and using Docker for consistent deployment.

Conclusion

Microservices architecture involves developing applications as a collection of small, loosely coupled services. By understanding and implementing the key concepts, examples, and best practices covered in this guide, you can effectively build and manage microservices with Express.js. Happy coding!