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
- John wants to use our application, and opens it in the browser.
- On the first page, he selects a nickname use during chat, and logs in to chat.
- In the text area he writes something and presses Enter.
- The text is sent to a RESTful service (Express) and this text is written to MongoDB.
- 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 text
, createDate
, 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:
{ text: "Hi, is there any Full Stack Developer here?" author: "john_the_full_stack", createDate: "2015.05.15" }
In order to create documents like this, you can implement a model by using the Mongoose functions below:
var mongoose = require('mongoose') var Message = new mongoose.Schema({ author: String, message: String, createDate: { type: Date, default: Date.now } }); 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.
..... app.get('/chat', function(req, res){ res.sendFile(__dirname + '/index.html'); }); app.get('/login', function(req, res){ res.sendFile(__dirname + '/login.html'); }); app.post('/messages', function(req, res, next) { var message = req.body.message; var author = req.body.author; var messageModel = new Message(); messageModel.author = author; messageModel.message = message; messageModel.save(function (err, result) { if (!err) { Message.find({}).sort('-createDate').limit(5).exec(function(err, messages) { io.emit("message", messages); }); res.send("Message Sent!"); } else { res.send("Technical error occurred!"); } }); }); app.get('/messages', function(req, res, next) { Message.find({}).sort('-createDate').limit(5).exec(function(err, messages) { res.json(messages); }); }); .....
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.
app.get('/chat', function(req, res){ res.sendFile(__dirname + '/index.html'); }); app.get('/login', function(req, res){ res.sendFile(__dirname + '/login.html'); });
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.
<script> var socket = io(); socket.on("message", function (messages) { refreshMessages(messages); }); function refreshMessages(messages) { $(".media-list").html(""); $.each(messages.reverse(), function(i, message) { $(".media-list").append('<li class="media"><div class="media-body"><div class="media"><div class="media-body">' + message.message + '<br/><small class="text-muted">' + message.author + ' | ' + message.createDate + '</small><hr/></div></div></div></li>'); }); } $(function(){ if (typeof $.cookie("realtime-chat-nickname") === 'undefined') { window.location = "/login" } else { $.get("/messages", function (messages) { refreshMessages(messages) }); $("#sendMessage").on("click", function() { sendMessage() }); $('#messageText').keyup(function(e){ if(e.keyCode == 13) { sendMessage(); } }); } function sendMessage() { $container = $('.media-list'); $container[0].scrollTop = $container[0].scrollHeight; var message = $("#messageText").val(); var author = $.cookie("realtime-chat-nickname"); $.post( "/messages", {message: message, author: author}, function( data ) { $("#messageText").val("") }); $container.animate({ scrollTop: $container[0].scrollHeight }, "slow"); } }) </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.
3. Deployment
3.1. Prerequisites
The first thing that comes to my mind is to show you how to deploy, but for successful deployment, we need a working database. Let's have a look at how to create a database on Modulus and then perform deployment.
Go to the Modulus dashboard after creating an account. Click the Databases menu on the left, and click Create Database.
Fill in the required fields in the popup form as below.
When you fill in the required fields and click Create, it will create a MongoDB database for you, and you will see your database URL on the screen. We will use MONGO URI, so copy that URI.
In our project, Mongo URI is fetched from the environment variable MONGO_URI
, and you need to set that environment variable in the dashboard. Go to the dashboard, click the Projects menu, select your project in the list, and click Administration in the left menu. In this page, you will see the environment variables section when you scroll down the page, as shown below.
You can deploy to Modulus in two ways:
- uploading the project ZIP file by using the dashboard
- deployment from the command line by using Modulus CLI
I will continue with the command line option, because the other one is easy to do. First of all, install Modulus CLI:
npm install -g modulus
Go to your project folder and perform the following command to log in to Modulus.
modulus login
When you perform the above command, you will be prompted to enter a username and password:
If you have created an account by using GitHub, you can use the --github
option.
modulus login --github
Now you are logged in to Modulus, and it's time to create a project. Use the following command to create a project:
modulus project create "Realtime Chat"
When you run this function, you will be asked for the runtime. Select the first option, which is Node.js, and second you will be asked for the servo size, and you can keep it as default.
We have created a project, and this time we will deploy our current project to Modulus. Execute the following command to send the current project to the Realtime Chat project on the Modulus side.
modulus deploy
It will deploy your project and you will get your running project URL at the end of the successful deployment message:
Realtime Chat running at realtime-chat-46792.onmodulus.net
As you can see, the deployment to Modulus is very easy!
Modulus CLI has very helpful commands to use during your project deployment or at runtime. For example, in order to tail logs of your running project, you can use modulus project logs tail
, to create a MongoDB database use modulus mongo create <db-name>
, to set an environment variable use modulus env set <key> <value>
, etc. You can see a full list of commands by using Modulus help.
Conclusion
The main purpose of this tutorial was to show you how to create a real-time chat application with Node.js, Socket.IO, and MongoDB. In order to run a project in production, Modulus is used as a PaaS provider. Modulus has very simple steps for deployment, and it also has an internal database (MongoDB) for our projects. Beside this, you can use very helpful tools inside the Modulus dashboard like Logs, Notifications, Auto-Scaling, Database Administration, and so on.
To sign up for Modulus click here and get an extra $10 exclusively for being a Tuts+ reader. Use promo code ModulusChat10.
For more information on the Modulus enterprise offering click here.
Comments