DEV Community

Eze Sunday Eze
Eze Sunday Eze

Posted on

How to Build a Real-time Chat App With NodeJS, Socket.IO, and MongoDB

How to Build a Real-time Chat App With NodeJS, Socket.IO, and MongoDB

In this tutorial, we’ll be building a real-time chat application with NodeJS, Express, Socket.io, and MongoDB.

Here is a screenshot of what we'll build:

alt text

Setup

I’ll assume that you already have NodeJS and NPM installed. You can install it from the Node JS website if you don’t have it installed already.

A basic Knowledge of Javascript is required.

Let’s get started.

Create a directory for the application, open the directory with your favourite editor such as Visual Studio Code. You can use any other editor, I’ll be using VS code in this tutorial:

mkdir chatApplication && cd chatApplication && code . 

Next, let’s initialize the directory as a Nodejs application.

 npm init 

You’ll be prompted to fill in some information — that’s okay. The information will be used to set up your package.json file.

Dependencies Installation

Let's install our application's dependencies.

We'll be using the express web server to serve our static files and body-parser extract the entire body portion of an incoming request stream and exposes it to an API endpoint. So, let's install them. You'll see how they are used later in this tutorial.

 npm install express body-parser --save 

We added the --save flag so that it’ll be added as a dependency in our package.json file.

Note:

Please, don’t use express generator as I won’t cover how to configure socket.io to work with express generator setup.

Next, install the mongoose node module. It is an ODM (Object Document Mapper) for MongoDB and it'll make our job a lot easier.

Let’s install it alongside socket.io and bluebird. Socket.IO is a JavaScript library for real-time web applications. Bluebird is a fully-featured Promise library for JavaScript.

 npm install mongoose socket.io bluebird --save 

That’s it for the Nodejs backend module installation.

Our package.json file should look like this now.

{
    "name": "chatApplication",
    "version": "1.0.0",
    "description": "",
    "main": "app.js",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node app"
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "bluebird": "^3.5.3",
        "body-parser": "^1.18.3",
        "express": "^4.16.4",
        "mongoose": "^5.4.14",
        "socket.io": "^2.2.0"
    }
}

Another way to install the above packages is to copy the package.json file above and paste it into your package.json file and run:

npm install

It'll install all the required packages.

Let’s set up the client side.

<!doctype  html>
<html>
    <head>
        <title>Anonymouse Real-time chat</title>
        <link  href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css"  rel="stylesheet"  id="bootstrap-css">
        <!------ Include the above in your HEAD tag ---------->
        <link  href="/css/style.css"  type="text/css"  rel="stylesheet"/>
    </head>
<body>
<div  class="chat_window">
    <div  class="top_menu">
    <div  class="buttons">
    <div  class="button close"></div>
    <div  class="button minimize"></div>
    <div  class="button maximize"></div>
</div>
    <div  class="title">Chat</div>
</div>
    <ul id="messages"  class="messages"></ul>
<div  class="bottom_wrapper clearfix">
<i  id="typing"></i>
    <form  id="form">
        <div  class="message_input_wrapper">
        <input  id="message"  class="message_input"  placeholder="Type your message here..."  />
        </div>
        <button  class="send_message">Send</button>
    </form>
</div>
</div>
<script  src="/js/socket.js"></script>
<script  src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<script  src="https://cdn.jsdelivr.net/gh/rexeze/formatTimeStamp/src/index.min.js"></script>
<script  src="/js/chat.js"></script>
</body>
</html>

To connect Socket.IO server to the client we add the Socket.IO client-side javascript library.

<script  src="/js/socket.js"></script>

That will be our html file for the frontend. You can grab the entire code for the frontend here to follow along. The best way to learn is to follow along.

You can download the client-side socket.io library here.

And here /js/chat.js is where we’ll have our custom client-side javascript code.

Setting up our express server:

Create an App.js. You can call it server.js if you like.
It’s my personal preference to call it App.js.

Inside the App.js file let’s create and configure the express server to work with socket.io.

App.js

//Require the express moule
const express = require(express);

//create a new express application
const app = express()

//require the http module
const http = require(http).Server(app)

// require the socket.io module
const io = require(socket.io);

const port = 500;

const socket = io(http);
//create an event listener

//To listen to messages
socket.on(connection, (socket)=>{
console.log(user connected);
});

//wire up the server to listen to our port 500
http.listen(port, ()=>{
console.log(connected to port: + port)
});

This is the basic configuration required to set up socket.io in the backend.

Socket.IO works by adding event listeners to an instance of http.Server
which is what we are doing here:

const socket = io(http);

Here is where we listen to new connection events:

socket.on(connection, (socket)=>{
console.log(user connected);
});

For example, if a new user visits localhost:500 the message "user connected" will be printed on the console.

socket.on() takes an event name and a callback as parameters.

And there is also a special disconnect event that gets fire each time a user closes the tab.

socket.on(connection, (socket)=>{
    console.log(user connected);
    socket.on("disconnect", ()=>{
    console.log("Disconnected")
})
});

Setting up our frontend code

Open up your js/chat.js file and type the following code:

(function() {
    var  socket  =  io();
    $("form").submit(function(e) {
        e.preventDefault(); // prevents page reloading
        socket.emit("chat message", $("#m").val());
        $("#m").val("");
    return  true;
});
})();

This is a self-executing function it initializes socket.io on the client side and emits the message typed into the input box.

With this line of code, we create a global instance of the soicket.io client on the frontend.

 var  socket  =  io();

And inside the submit event handler, socket io is getting our chat from the text box and emitting it to the server.

$("form").submit(function(e) {
    e.preventDefault(); // prevents page reloading
    socket.emit("chat message", $("#m").val());
    $("#m").val("");
 return  true;
});

If you've gotten to this point, congratulations, you deserve some accolades.
😄

Great, we have both our express and socket.io server set up to work well. In fact, we've been able to send messages to the server by emitting the message from our input box.

socket.emit("chat message", $("#m").val());

Now from the server-side let's set up an event to listen to the "chat message" event and broadcast it to clients connected on port 500.

App.js

socket.on("chat message", function(msg) {
console.log("message: "  +  msg);
//broadcast message to everyone in port:5000 except yourself.
socket.broadcast.emit("received", { message: msg  });
});
});

This is the event handler that listens to the "chat message" event and the message received is in the parameter passed to the callback function.

socket.on("chat message", function(msg){
});

Inside this event, we can choose what we do with the message from the client ---insert it into the database, send it back to the client, etc.

In our case, we'll be saving it into the database and also sending it to the client.

We'll broadcast it. That means the server will send it to every other person connected to the server apart from the sender.

So, if Mr A sends the message to the server and the server broadcasts it, Mr B, C, D, etc will receive it but Mr A won't.

We don't want to receive a message we sent, do we?😭

That doesn't mean we can't receive a message we sent as well. If we remove the broadcast flag we'll also remove the message.

Here is how to broadcast an event:

socket.broadcast.emit("received",{message:msg})

With that out of the way, we can take the message received and append it to our UI.

If you run your application. You should see something similar to this. Please, don't laugh at my live chat. ❤️

Wawu! Congratulations once again. let's add some database stuff and display our chats on the frontend.

Database Setup

Install MongoDB

Visit the mongoDB website to download it if you have not done so already.

And make sure your MongoDB server is running. They have an excellent documentation that details how to go about setting it up and to get it up and running. You can find the doc here.

Create Chat Schema

Create a file in the model's directory called models/ChatSchema.js
Nothing complex, we are just going to have 3 fields in our schema --- a message field, a sender field and a timestamp.

The ChatSchema.js file should look like this:


const  mongoose  = require("mongoose");
const  Schema  =  mongoose.Schema;
const  chatSchema  =  new Schema(
    {
    message: {
    type: String
    },
    sender: {
    type: String
        }
    },
        {
    timestamps: true
});

let  Chat  =  mongoose.model("Chat", chatSchema);
module.exports  =  Chat;

Connection to the mongodb database

Create a file and name it dbconnection.js. That's where our database connection will live.

const  mongoose  = require("mongoose");
mongoose.Promise  = require("bluebird");
const  url  =  "mongodb://localhost:27017/chat";
const  connect  =  mongoose.connect(url, { useNewUrlParser: true  });
module.exports  =  connect;

Insert messages into the database

Since we are going to insert the messages in the server-side we'll be inserting the messages we receive from the frontend in the App.js file.

So, let's update the App.js file.


...
//database connection
const  Chat  = require("./models/Chat");
const  connect  = require("./dbconnect");


//setup event listener
socket.on("connection", socket  =>  {
    console.log("user connected");
    socket.on("disconnect", function() {
    console.log("user disconnected");
    });  
    socket.on("chat message", function(msg) {
        console.log("message: "  +  msg);
        //broadcast message to everyone in port:5000 except yourself.
    socket.broadcast.emit("received", { message: msg  });

    //save chat to the database
    connect.then(db  =>  {
    console.log("connected correctly to the server");

    let  chatMessage  =  new Chat({ message: msg, sender: "Anonymous"});
    chatMessage.save();
    });
    });
});

We are creating a new document and saving it into the Chat collection in the database.


    let  chatMessage  =  new Chat({ message: msg, sender: "Anonymous"});
    chatMessage.save();

Display messages on the frontend

We'll, first of all, display our message history from the database and append all messages emitted by events.

To achieve this, we need to create an API that sends the data from the database to the client-side when we send a get request.

const  express  = require("express");
const  connectdb  = require("./../dbconnect");
const  Chats  = require("./../models/Chat");

const  router  =  express.Router();

router.route("/").get((req, res, next) =>  {
        res.setHeader("Content-Type", "application/json");
        res.statusCode  =  200;
        connectdb.then(db  =>  {
            Chats.find({}).then(chat  =>  {
            res.json(chat);
        });
    });
});

module.exports  =  router;

In the above code, we query the database and fetch all the messages in the Chat collection.

We'll import this into the server code App.js file and we'll also import the bodyparser middleware as well.

const  bodyParser  = require("body-parser");
const  chatRouter  = require("./route/chatroute");

//bodyparser middleware
app.use(bodyParser.json());

//routes
app.use("/chats", chatRouter);

With this out of the way, we are set to access our API from the frontend and get all the messages in our Chat collection.

// fetching initial chat messages from the database
(function() {
    fetch("/chats")
    .then(data  =>  {
    return  data.json();
    })
.then(json  =>  {
json.map(data  =>  {
let  li  =  document.createElement("li");
let messages = docuemtn.getElementById("messages")
let  span  =  document.createElement("span");
messages.appendChild(li).append(data.message);

    messages
    .appendChild(span)
    .append("by "  +  data.sender  +  ": "  +  formatTimeAgo(data.createdAt));
});
});
})();

So, we got the messages using the fetch API and we appended the messages to the UI.

You'll also notice that I used formatTimeAgo(data.createdAt)); that is a 1.31kb library I created to manage dates for small projects since moment.js sometimes is rather too big. formatTimeAgo() will display "few seconds ago", etc.

If you are interested, you can find more information here.

Everything seems good at this point, right?

However, since you are not receiving the messages sent to the server back to yourself, let's grab our own message from our input box and display it on the UI.

(function() {
$("form").submit(function(e) {
    let  li  =  document.createElement("li");
    e.preventDefault(); // prevents page reloading
    socket.emit("chat message", $("#message").val());
    messages.appendChild(li).append($("#message").val());
    let  span  =  document.createElement("span");
    messages.appendChild(span).append("by "  +  "Anonymous"  +  ": "  +  "just now");
    $("#message").val("");
return  false;

});
})();

And also if we receive messages from the event let's also output it to the UI.

(function(){
socket.on("received", data  =>  {
let  li  =  document.createElement("li");
let  span  =  document.createElement("span");
var  messages  =  document.getElementById("messages");
messages.appendChild(li).append(data.message);
messages.appendChild(span).append("by "  +  "anonymous"  +  ": "  +  "just now");
});
})

Our application is complete now. Go ahead an test it.

Note that if we had our users logged in we wouldn't have hardcoded the "anonymous" user as it's in our code right now. We'll get it from the server.

And also if you want to tell everyone that someone is typing you can also add this code in the frontend.

//isTyping event
messageInput.addEventListener("keypress", () =>  {
socket.emit("typing", { user: "Someone", message: "is typing..."  });
});
socket.on("notifyTyping", data  =>  {
typing.innerText  =  data.user  +  "  "  +  data.message;
console.log(data.user  +  data.message);
});
//stop typing
messageInput.addEventListener("keyup", () =>  {
socket.emit("stopTyping", "");
});
socket.on("notifyStopTyping", () =>  {
typing.innerText  =  "";

});

What it does is that when a user is typing it emits an event to the server and the server broadcasts it to other clients. You listen to the event and update the UI with the message "Someone is typing..." You can add the person's name if you wish.

Here is the server-side event listener and emitter:

 //Someone is typing

 socket.on("typing", data => { 

    socket.broadcast.emit("notifyTyping", { user: data.user, message: data.message }); }); 

//when soemone stops typing

socket.on("stopTyping", () => { socket.broadcast.emit("notifyStopTyping"); });

Congratulations.

You can improve this code, add authentication, add groups or make it a one to one chat, re-model the schema to accommodate all of that, etc.

I'll be super excited to see the real-time applications you'll build with socket.IO.

I hope this was helpful. The entire code is on Github. You can get it here.

Top comments (33)

Collapse
 
vickyktk profile image
vickyktk

realtimechatt.herokuapp.com/

Have a look at my effort

Collapse
 
seifeslimene profile image
Seif Eddine Slimene

Good Effort, can you share your code please?

Collapse
 
vickyktk profile image
vickyktk

It is on the Github

Thread Thread
 
rcorte666 profile image
rcorte666

Hello, you still have the code on github?

Thread Thread
 
vickyktk profile image
vickyktk

Yes it is there

Thread Thread
 
rcorte666 profile image
rcorte666

ty

Thread Thread
 
vickyktk profile image
vickyktk

Welcome

Collapse
 
ezesunday profile image
Eze Sunday Eze

Awesome. It looks nice.

Collapse
 
vickyktk profile image
vickyktk

Thanks man

Collapse
 
audreykremers profile image
Audrey Kremers

Hello,
Find out a very bad translation of your article into French and of course, without any mention of your name. So bad, that's made my angry! Anyway, thx to YOU for this good tutorial ! ;)
supportivy.com/comment-creer-une-a...

Collapse
 
ezesunday profile image
Eze Sunday Eze

Thanks, I have had similar issues several times where people copy my content. It's terrible. Thanks for letting me know. What I hate about it is that it's badly written and formatted.

Collapse
 
mrwilbroad profile image
wilbroad mark

Appreciated post👋

Collapse
 
shriyamadan profile image
Shriya Madan

Heya! Can you tell me how to add a feature for showing "online", once the user is active?
Great work!

Collapse
 
ezesunday profile image
Eze Sunday Eze • Edited

It's not straight forward though. different browsers implement this differently.

But you can do two things, use the offlineOnline browser api here

Ping a website you are sure will always be available such as Google in a http request. You can broadcast same event to other members saying the user is offline. That's it.

Collapse
 
shriyamadan profile image
Shriya Madan

chat-with-friends.herokuapp.com/
Here's my chat application. Check it, please :)

Thread Thread
 
ezesunday profile image
Eze Sunday Eze

Went through it. It seems to work really well. Good job.

Do you plan to make something much more bigger with this?

Thread Thread
 
ezesunday profile image
Eze Sunday Eze

Glad to see what you have build so far. Keep it up.

Thread Thread
 
shriyamadan profile image
Shriya Madan

Thank you! :) Yeah, I am looking for ideas to make something better out of it.

Collapse
 
g_mann43 profile image
Gavin

Would you not do presence by holding an array of users on server side and upon connection add the user to the array and broadcast to everyone/room...and on disconnect remove the user from the array and broadcast the disconnection status ?

Collapse
 
taiwosunday99 profile image
Taiwo

I will like to implement a real time database into my mongodb cluster. How do I integrate it easily without much hassle? i have a working database already though not in production yet.. any suggestion will help greatly. thanks

Collapse
 
danielsogbey profile image
Daniel-sogbey

How can I implement a one on one chat system

Collapse
 
thannsokkhen profile image
Thann SokKhen

That's the same as this one too. you only need a unique ID for a room that only 2 users can access and chat to each other.

Collapse
 
prashant1k99 profile image
Prashant Singh

Does anybody knows how to setup friend online indicator for chat app?

Collapse
 
ezesunday profile image
Eze Sunday Eze

You can start with the suggestion:

It's not straight forward though. different browsers implement this differently.

But you can do two things, use the offlineOnline browser api here

Ping a website you are sure will always be available such as Google in a http request. You can broadcast same event to other members saying the user is offline. That's it.

Collapse
 
polotent profile image
Alexander Miroshnichenko • Edited

It should be require("./dbconnection") in App.js, not require("./dbconnect").

And it should be require("./ChatSchema") in App.js, not require("./Chat").

Collapse
 
shriyamadan profile image
Shriya Madan

keypress jquery isn't working for mobile browser, so I used keydown instead.

Collapse
 
ezesunday profile image
Eze Sunday Eze

Good one there. The article is old, a lot has changed. Thanks for catching that.

Collapse
 
aryanpnd profile image
aryanpnd

can I connect this to my mongo dB compass??

Collapse
 
williamdarkocode profile image
William

Hey I was wondering if you had an example with similar architecture but for private messaging, or notifications. Thanks.

Collapse
 
ouddriss profile image
OUDDRISS

hey Mr Eze! thanks for sharing your chat project ! but i hve this error : (ReferenceError: localStorage is not defined)can you help me to resolve it plz thanks in advance

Collapse
 
nayyaung9 profile image
nay yaung lin lakk

Hello..I have an issue. If i reload the page in another browser, i got the duplicate messages. How can i fix this?

Collapse
 
zee10zee profile image
Abed Khan

Tnx alot for your service bro !
i really really appreciate it !!!!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.