
Creating a chat endpoint
Now that we have the server up and running, we can create an in-memory chat endpoint, which would accept a message from two users and forward it to its intended recipient using a queue while retaining the order.
Before we add the logic, we will need to do some groundwork to set up the application in a modular way. First, let's include the body-parser and use it in an express middleware so that we can access the body of requests easily. So, the updated index.js file looks as follows:
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', function (req, res) {
res.status(200).send('OK!')
});
app.listen(3000, function () {
console.log('Chat Application listening on port 3000!')
});
Now, to add the endpoint for the message, we can create a new file called messages.js under the routes folder to which we can add the basic post request:
var express = require('express');
var router = express.Router();
router.route('/')
.post(function(req, res) {
res.send(`Message received from: ${req.body.from} to ${req.body.to} with message ${req.body.message}`);
});
module.exports = router;
Then, we can inject it in our index.js and make it a part of our application:
var message = require('./routes/messages');
...
...
...
app.use('/message', message);
Now, to test this, we can start our server and post a message to localhost:3000/message using Postman; then we can see the response posted, as follows:

Now, we can go ahead and start adding the logic to send messages between two users. We are going to abstract, mock, and simplify the chat part of the application and focus more on queue applications in such complex applications.
The workflow itself is relatively straightforward: user A sends a message to user B, which our server tries to forward to user B. If it goes through without any issue, then everything is good, and the message is delivered to user B; but if it fails, then we invoke our FailureProtocol(), which retries to send the last failed message per-conversation. For simplicity, we will assume that there is only one channel right now, that is, between user A and user B.
The production counterpart of this would be capable of handling multiple channels simultaneously by creating a new FailureProtocol() handler for a particular channel when a message fails on a channel and would have the flexibility of deferring the job over to multiple threads.
Let's now mock out the sendMessage() and getUniqueFailureQueue() methods in a file called messaging-utils.js which will be our wrapper so that we can move them into their own module, as their internal workings are not really important to understand queues in this scenario:
var PriorityQueue = require('./priority-queue');
var Utils = (()=> {
class Utils {
constructor() {
}
getUniqueFailureQueue(from, to) {
// use from and to here to determine
// if a failure queue already
// exists or create a new one
return new PriorityQueue();
}
sendMessage(message) {
return new Promise(function(resolve, reject) {
// randomize successes and failure of message being
sent
if(Math.random() < 0.1) {
resolve(message)
} else {
reject(message);
}
});
}
}
return Utils;
})();
module.exports = Utils;
Now, when we receive a new message, we try to send it to the intended end user:
var express = require('express');
var router = express.Router();
var Utils = require('../utils/messaging-utils');
const msgUtils = new Utils();
router.route('/')
.post(function(req, res) {
const message = req.body.message;
let failedMessageQueue;
// try to send the message
msgUtils.sendMessage(req.body)
.then(function() {
res.send(`Message received from: ${req.body.from} to ${req.body.to} with message ${req.body.message}`);
}, function() {
failedMessageQueue =
msgUtils.getUniqueFailureQueue(req.body.from,
req.body.to);
failedMessageQueue.add(message);
// trigger failure protocol
triggerFailureProtocol();
});
If the message is sent successfully, we will need to immediately acknowledge that and send a success message—otherwise, we will get a unique failedMessageQueue between the two users—and then add the message to it, which is then followed by triggering the failure protocol.
A failure protocol can mean anything to different applications. While some applications choose to just show a failed message, applications such as ours will retry to send the message until it is sent successfully:
function triggerFailureProtocol() {
var msg = failedMessageQueue.front();
msgUtils.sendMessage(msg)
.then(function() {
failedMessageQueue.remove();
res.send('OK!');
}, function(msg) {
//retry failure protocol
triggerFailureProtocol();
});
}
We can use the methods available in our Queue to pick the top message and then try to send it. If successful in doing so, then remove it; otherwise, retry. As you can see, using queues greatly simplifies and abstracts the logic of the actual queuing of failed messages and what is even better is that you can upgrade and enhance the queue at any time without having to think twice about what other components would get affected by that change.
Now that we have the API call ready to parse the incoming request, send it to the intended recipient and trigger our custom failure protocol. When we combine all of this logic together, we have the following:
var express = require('express');
var router = express.Router();
var Utils = require('../utils/messaging-utils');
const msgUtils = new Utils();
router.route('/')
.post(function(req, res) {
const message = req.body.message;
let failedMessageQueue;
// try to send the message
msgUtils.sendMessage(req.body)
.then(function() {
console.log("Sent Successfully : " + message);
res.send(`Message received from: ${req.body.from} to ${req.body.to} with message ${req.body.message}`);
}, function(msg) {
console.log('Failed to send: ' + message);
failedMessageQueue =
msgUtils.getUniqueFailureQueue(req.body.from,
req.body.to);
failedMessageQueue.add(message);
// trigger failure protocol
triggerFailureProtocol();
});
function triggerFailureProtocol() {
var msg = failedMessageQueue.front();
msgUtils.sendMessage(msg)
.then(function() {
failedMessageQueue.remove();
res.send('OK!');
}, function(msg) {
//retry failure protocol
triggerFailureProtocol();
});
}
});
module.exports = router;