DEV Community

Omar Diaaeldine Elwakeel
Omar Diaaeldine Elwakeel

Posted on

Microservices with express.js and rabbitmq

This article is to simplify and do a step by step tutorial to make a microservices app

In this tutorial, I'm going to use

  1. Node.js as runtime environment.
  2. Typescript instead of Javascript for better development experience.
  3. Express.js for the backend.
  4. Rabbitmq as a message broker.

To find the final code, you can find it on Github

Before typing code, why Microservices?

Let's say we have to services, In a Monolithic Architecture you would have only one repository and all your services interact through the same instance, if any service caused the instance to crash, the whole application will crash.

While in a Microservices Architecture each will work a whole unit and on interacting with other services, it uses message brokers like Rabbitmq to send a message to other services.

Now consider a warehouse service (consumer) that really depends on an order service (publisher), where whenever an order is submitted the warehouse is notified and starts the shipping process, in this architecture, if the order service caused a crash, the warehouse won't be affected and will keep working, even if the warehouse service was out when an order is submitted, the message broker will keep the message and on the startup of the warehouse service, it will assert that in knows about this message.

How to implement it?

First, the folder structure, consider making a directory called online-ordering-microservices, with two folders inside [order, warehouse]

Folder structure

The .gitingore file to ignore the node_modules inside each service

Second, setup rabbitmq as a message broker

Run docker container run --name rabbitmq --detach -p 5672:5672 rabbitmq

This will run a container from the image rabbitmq, on a detached mode and expose the port 5672 which is the default port for rabbitmq and give this container a name rabbitmq

Third, setup each service.

Run npm install express @types/express amqplib @types/amqplib to install the necessary dependencies.

order/index.ts


import amqplib, { Channel, Connection } from 'amqplib'
import express, { Request, Response } from 'express'

const app = express()

// parse the request body
app.use(express.json())

// port where the service will run
const PORT = 9005

// rabbitmq to be global variables
let channel: Channel, connection: Connection

connect()

// connect to rabbitmq
async function connect() {
  try {
      // rabbitmq default port is 5672
    const amqpServer = 'amqp://localhost:5672'
    connection = await amqplib.connect(amqpServer)
    channel = await connection.createChannel()

    // make sure that the order channel is created, if not this statement will create it
    await channel.assertQueue('order')
  } catch (error) {
    console.log(error)
  }
}

app.post('/orders', (req: Request, res: Response) => {
  const data = req.body

  // send a message to all the services connected to 'order' queue, add the date to differentiate between them
  channel.sendToQueue(
    'order',
    Buffer.from(
      JSON.stringify({
        ...data,
        date: new Date(),
      }),
    ),
  )

  res.send('Order submitted')
})

app.get('*', (req: Request, res: Response) => {
  res.status(404).send('Not found')
})

app.listen(PORT, () => {
  console.log(`Server running on ${PORT}`)
})


Enter fullscreen mode Exit fullscreen mode

warehouse/index.ts


import amqplib, { Channel, Connection } from 'amqplib'
import express, { Request, Response } from 'express'

const app = express()

// parse the request body
app.use(express.json())

// port where the service will run
const PORT = 9005

// rabbitmq to be global variables
let channel: Channel, connection: Connection

connect()

async function connect() {
  try {
    const amqpServer = 'amqp://localhost:5672'
    connection = await amqplib.connect(amqpServer)
    channel = await connection.createChannel()

    // consume all the orders that are not acknowledged
    await channel.consume('order', (data) => {
      console.log(`Received ${Buffer.from(data!.content)}`)
      channel.ack(data!);
    })
  } catch (error) {
    console.log(error)
  }
}

app.get('*', (req: Request, res: Response) => {
  res.status(404).send('Not found')
})

app.listen(PORT, () => {
  console.log(`Server running on ${PORT}`)
})


Enter fullscreen mode Exit fullscreen mode

Now, if you run both apps and made a post request to http://localhost:9005/orders, you will get a message in the warehouse service, more importantly, if you made a request while the warehouse service is not running and started the warehouse service, it will receive that message, and actually will keep receiving it untill it acknowledges it.

I hope you liked this tutorial, and see you in another ones.

Oldest comments (2)

Collapse
 
nithinsikinam profile image
nithinsikinam

What if we wanted a reply from warehouse how to setup that .

Collapse
 
promise111 profile image
Promise Ihunna • Edited

I am yet to try it out too. But logically, since. we are just passing streams of data to a queue, you can assert a queue in the warehouse service and run the logic again after the initial data from order is consumed.