How to use ZeroMQ Request-Reply Pattern in Node.js
Francisco Mendes
Posted on September 11, 2021
Overview
I bet many of us have thought about decopulating a backend and splitting it into microservices. Let's say you have a monolithic backend and then you decide to add something like a newsletter and you'd rather have a microservice that has the sole function of sending emails.
On the internet you will find several solutions to solve this challenge, but one of the most common is the use of a message broker. However, not all of us need a solution as advanced as the use of a message broker, it is in these specific cases (smaller applications) that I like to use ZeroMQ.
If you don't know ZeroMQ, that's okay because it's a technology that isn't widely shared in the community, so if you want to know more about ZeroMQ, I recommend reading this article, which will give you a better introduction than me.
Today's example
The idea of today's example is to create a simple Api (server) that will receive a json property that will be the text and then we will send that same text to an app (worker) that will be responsible for calculating the length of this same string. Finally we will return the string length to the Api and then it is sent in the response body.
The framework I'm going to use is Fastify and the ZeroMQ client that I'm going to use is zeromq.
Let's code
As you may have already understood, we are going to have two backends. One of the backends we will call a server, which will be our Api. The other backend will be the worker, which will be our small microservice.
First and foremost, let's install our dependencies:
npm install fastify zeromq --save
Now let's create a simple API:
// @/server.js
const Fastify = require("fastify");
const app = Fastify();
app.post("/", (request, reply) => {
const { text } = request.body;
return reply.send({ text });
});
const main = async () => {
try {
await app.listen(3000);
} catch (err) {
console.error(err);
process.exit(1);
}
};
main();
As you can see, let's get the text from the request body object and then send it in the response body.
Now we can import zeromq and create an instance of it. Then we will create our ZeroMQ socket of the Request type and we will accept connections through an address defined by us, however this is asynchronous and has to be done as soon as the application is started. Like this:
// @/server.js
const Fastify = require("fastify");
const zmq = require("zeromq");
const app = Fastify();
const sock = new zmq.Request();
// ...
const main = async () => {
try {
await sock.bind("tcp://*:7777");
await app.listen(3000);
} catch (err) {
console.error(err);
process.exit(1);
}
};
main();
Afterwards at our endpoint we will send the text we got in the body of the request object, however before we send it we have to convert the json to string.
app.post("/", async (request, reply) => {
const { text } = request.body;
await sock.send(JSON.stringify({ text }));
return reply.send({ text });
});
Still on our endpoint, after we send the text we'll wait for the response from our worker.
app.post("/", async (request, reply) => {
const { text } = request.body;
await sock.send(JSON.stringify({ text }));
const [result] = await sock.receive();
return reply.send({ text });
});
Now we have to be careful because what we're going to get from our worker is a buffer so we have to handle it to get the text length value.
After that we have to convert the result from string to number, only after that we can send the result in the response body.
app.post("/", async (request, reply) => {
const { text } = request.body;
await sock.send(JSON.stringify({ text }));
const [result] = await sock.receive();
return reply.send({ length: Number(Buffer.from(result)) });
});
Our server code should look like this:
// @/server.js
const Fastify = require("fastify");
const zmq = require("zeromq");
const app = Fastify();
const sock = new zmq.Request();
app.post("/", async (request, reply) => {
const { text } = request.body;
await sock.send(JSON.stringify({ text }));
const [result] = await sock.receive();
return reply.send({ length: Number(Buffer.from(result)) });
});
const main = async () => {
try {
await sock.bind("tcp://*:7777");
await app.listen(3000);
} catch (err) {
console.error(err);
process.exit(1);
}
};
main();
Now we can start working on our worker. Now let's import zeromq and create an instance of it. Then we will create our ZeroMQ socket of the Reply type and we will accept connections through the address that we defined before.
// @/worker.js
const zmq = require("zeromq");
const sock = new zmq.Reply();
const main = async () => {
try {
sock.connect("tcp://localhost:7777");
// ...
} catch (err) {
console.error(err);
process.exit(1);
}
};
main();
Then let's create a for loop so that it calculates each of the messages we receive via our server. Once we get the message we have to convert it from string back to json so we can read the text value and then we will log it.
After this we will calculate the length of it, but we have to take into account that before sending the length of the text we have to convert the value to string.
// @/worker.js
const zmq = require("zeromq");
const sock = new zmq.Reply();
const main = async () => {
try {
sock.connect("tcp://localhost:7777");
for await (const [msg] of sock) {
const text = JSON.parse(msg).text;
console.log(`Received: ${text}`);
await sock.send(String(text.length));
}
} catch (err) {
console.error(err);
process.exit(1);
}
};
main();
Now when testing our Api with a tool similar to Postman, you can send a json object in the request body with the text property.
Then you should have something similar to this on your terminal:
Conclusion
As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻💻
Hope you have a great day! 👋
Posted on September 11, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
April 15, 2022