Thursday, August 18, 2016

Real-Time Chat With Modulus and Node.js_part1

In this tutorial, I will show you how to implement a real-time chat application with Node.js, Socket.IO and MongoDB, and then we will deploy this application to Modulus together.

First of all, let me show you the final look of the application that we will have at the end of the article.


Node.js will be the nucleus of the application, with Express as the MVC, MongoDB for the database, and Socket.IO for real-time communication. When we've finished, we will deploy our application to Modulus. The MongoDB part actually exists inside Modulus.

1. Scenario
  1. John wants to use our application, and opens it in the browser.
  2. On the first page, he selects a nickname use during chat, and logs in to chat.
  3. In the text area he writes something and presses Enter.
  4. The text is sent to a RESTful service (Express) and this text is written to MongoDB.
  5. Before writing in MongoDB, the same text will be broadcast to the users that are currently logged in to the chat app.
As you can see, this is a very simple app, but it covers almost everything for a web application. There is no channel system in this application, but you can fork the source code and implement the channel module for practice.

2. Project Design From Scratch

I will try to explain the small pieces of the project first and combine them at the end. I will start from the back end to the front end. So, let's start with the domain objects (MongoDB models).

2.1. Model

For database abstraction, we will use Mongoose. In this project, we have only one model called Message. This message model only contains textcreateDate, and author. There is no model for the author like User, because we will not fully implement a user registration/login system. There will be a simple nickname-providing page, and this nickname will be saved to a cookie. This will be used in the Message model as text in the author field. You can see an example JSON model below:
  1. {
  2.     text: "Hi, is there any Full Stack Developer here?"
  3.     author: "john_the_full_stack",
  4.     createDate: "2015.05.15"
  5. }
In order to create documents like this, you can implement a model by using the Mongoose functions below:
  1. var mongoose = require('mongoose')
  2.  
  3. var Message = new mongoose.Schema({
  4.     author: String,
  5.     message: String,
  6.     createDate: {
  7.         type: Date,
  8.         default: Date.now
  9.     }
  10. });
  11.  
  12. mongoose.model('Message', Message)
Simply import the Mongoose module, define your model with its fields and field attributes in JSON format, and create a model with the name Message. This model will be included in the pages that you want to use.

Maybe you have a question about why we are storing the message in the database, when we already broadcast this message to the user in the same channel. It's true that you do not have to store chat messages, but I just wanted to explain the database integration layer. Anyway, we will use this model in our project inside the controllers. Controllers?

2.2. Controller

As I said earlier, we will use Express for the MVC part. And C here stands for the Controller. For our projects, there will be only two endpoints for messaging. One of them is for loading recent chat messages, and the second one is for handling sent chat messages to store in the database, and then broadcast into the channel.
  1. .....
  2. app.get('/chat', function(req, res){
  3.     res.sendFile(__dirname + '/index.html');
  4. });
  5.  
  6. app.get('/login', function(req, res){
  7.     res.sendFile(__dirname + '/login.html');
  8. });
  9.  
  10. app.post('/messages', function(req, res, next) {
  11.     var message = req.body.message;
  12.     var author = req.body.author;
  13.     var messageModel = new Message();
  14.     messageModel.author = author;
  15.     messageModel.message = message;
  16.     messageModel.save(function (err, result) {
  17.        if (!err) {
  18.            Message.find({}).sort('-createDate').limit(5).exec(function(err, messages) {
  19.                io.emit("message", messages);
  20.            });
  21.            res.send("Message Sent!");
  22.        } else {
  23.            res.send("Technical error occurred!");
  24.        }
  25.     });
  26. });
  27.  
  28. app.get('/messages', function(req, res, next) {
  29.     Message.find({}).sort('-createDate').limit(5).exec(function(err, messages) {
  30.         res.json(messages);
  31.     });
  32. });
  33. .....
The first and second controllers are just for serving static HTML files for the chat and login pages. The third one is for handling the post request to the /messages endpoint for creating new messages. In this controller, first of all the request body is converted to the Message model, and then this model is saved to the database by using the Mongoose function save.

I will not dive into Mongoose very much—you can have a look at the documentation for further details. You can provide a callback function for the save function to check whether there is any problem or not. If it is successful, we have fetched the last five records sorted in descending order by createDate, and have broadcast five messages to the clients in the channel.

Ok, we have finished MC. Let's switch to the View part.

2.3. View

In general, a template engine like Jade, EJS, Handlebars, etc., can be used within Express. However, we have only one page, and that is a chat message, so I will serve this statically. Actually, as I said above, there are two more controllers to serve this static HTML page. You can see the following for serving a static HTML page.
  1. app.get('/chat', function(req, res){
  2.     res.sendFile(__dirname + '/index.html');
  3. });
  4.  
  5. app.get('/login', function(req, res){
  6.     res.sendFile(__dirname + '/login.html');
  7. });
This endpoint simply serves index.html and login.html by using res.sendFile. Both index.html and login.html are in the same folder as server.js, which is why we used __dirname before the HTML file name.

2.4. Front End

In the front-end page, I have used Bootstrap and there is no need to explain how I managed to do that. Simply, I have bound a function to a text box, and whenever you press the Enter key or Send button, the message will be sent to the back-end service.

This page also has a required js file of Socket.IO to listen to the channel called message. The Socket.IO module is already imported in the back end, and when you use this module in the server side, it automatically adds an endpoint for serving the Socket.IO js file, but we use the one that is served from cdn <script src="//cdn.socket.io/socket.io-1.3.5.js"></script>. Whenever a new message comes in to this channel, it will automatically be detected and the message list will be refreshed with the last five messages.
  1. <script>
  2.         var socket = io();
  3.         socket.on("message", function (messages) {
  4.             refreshMessages(messages);
  5.         });
  6.  
  7.         function refreshMessages(messages) {
  8.             $(".media-list").html("");
  9.             $.each(messages.reverse(), function(i, message) {
  10.                 $(".media-list").append('<li class="media"><div class="media-body"><div class="media"><div class="media-body">'
  11.                 + message.message + '<br/><small class="text-muted">' + message.author + ' | ' + message.createDate + '</small><hr/></div></div></div></li>');
  12.             });
  13.         }
  14.  
  15.         $(function(){
  16.  
  17.             if (typeof $.cookie("realtime-chat-nickname") === 'undefined') {
  18.                 window.location = "/login"
  19.             } else {
  20.                 $.get("/messages", function (messages) {
  21.                     refreshMessages(messages)
  22.                 });
  23.  
  24.                 $("#sendMessage").on("click", function() {
  25.                     sendMessage()
  26.                 });
  27.  
  28.                 $('#messageText').keyup(function(e){
  29.                     if(e.keyCode == 13)
  30.                     {
  31.                         sendMessage();
  32.                     }
  33.                 });
  34.             }
  35.  
  36.             function sendMessage() {
  37.                 $container = $('.media-list');
  38.                 $container[0].scrollTop = $container[0].scrollHeight;
  39.                 var message = $("#messageText").val();
  40.                 var author = $.cookie("realtime-chat-nickname");
  41.                 $.post( "/messages", {message: message, author: author}, function( data ) {
  42.                     $("#messageText").val("")
  43.                 });
  44.                 $container.animate({ scrollTop: $container[0].scrollHeight }, "slow");
  45.             }
  46.         })
  47.     </script>
There is one more check in the above code: the cookie part. If you have not chosen any nickname for chat, it means the cookie is not set for the nickname, and you will be automatically redirected to the login page.

If not, the last five messages will be fetched by a simple Ajax call to the /messages endpoint. In the same way, whenever you click the Send button or press the Enter key, the text message will be fetched from the text box, and the nickname will be fetched from the cookie, and those values will be sent to the server with a post request. There is no strict check for the nickname here, because I wanted to focus on the real-time part, not the user authentication part.

As you can see, the overall structure of the project is very simple. Let's come to the deployment part. As I said earlier, we will use Modulus, one of the best PaaS for deploying, scaling and monitoring your application in the language of your choice.
(continue)

If you found this post interesting, follow and support us.
Suggest for you:

Complete Node JS Developer Course Building 5 Real World Apps

Node.js Tutorials: The Web Developer Bootcamp

Learn How To Deploy Node.Js App on Google Compute Engine

Learn and Understand NodeJS

Learn Nodejs by Building 12 Projects

No comments:

Post a Comment