Building a chat room in 30 minutes using Redis, Socket.io and Express
You are relaxing and listening to music in your office, then all of a sudden your desk phone rings, your boss hurriedly says “We need a chat room built and ready in less than 30 minutes to discuss a new project we are going to work on soon”. As the only developer around, you said “Ready in a bit!” with no idea about how to build one.
Don’t worry, get ready to build a simple chat room using Manifold, Express, Socket.io and Zeit. This tutorial will show you how easy it is to use one of Redis’ awesome feature called pub/Sub with Socket.io, to send and receive messages. Time to get to the action!
Quick Intro on Redis
Redis, which means REmote DIctionary Server, is an open source, in-memory data structure store. It can be used as a key-value database, cache and message broker. It supports a wide range of data structures such as strings, hashes, sets, lists etc. It also has on-disk persistence, which can be achieved by regularly dumping data to the disk or by appending commands to a log.
However, we are interested in one of Redis feature called Pub/Sub. Redis Pub/Sub allows a publisher (sender) send a message to a channel without knowing if there is any interested subscriber (receiver). Also, a subscriber expresses interest in a channel to receive messages without any knowledge of a publisher. Basically, a publisher is like a satellite antenna that sends out messages into deep space without knowing if aliens exist to receive the messages, while a subscriber is like a base station on Earth listening on a radio channel hoping to receive a message from deep space, without knowing if aliens are broadcasting.
Publishers and Subscribers are decoupled to make the process very fast and improve scalability since both subscribers and publishers are not aware of each other.
Pros of using Redis PubSub
- It is very fast since it makes use of in-memory data stores.
- Slow subscribers can not delay publishers from broadcasting messages since it is not queue based.
- The simplicity allows users to be flexible and easily scale applications
Cons of using Redis PubSub
- It is not capable of persistence, which means messages are not saved or cached. Once a subscriber misses a message, there is no way it can get the message again. However, there are measures you can put in place to compensate, as we will see later in this tutorial.
You can read up on Redis here.
Set up
First, we need to set up Redis. Instead of spending time installing and configuring Redis on your server, you can head over to Manifold and create an instance. If you don’t have a Manifold account, you can quickly create one.
Once you are logged in, create a new project and provision a RedisGreen resource, this shouldn’t take very much time.
Click on Download .env
button once the resource has been created, to download the .env
file containing the credentials.
Copy the downloaded .env
file and paste it in your project root directory.
Install modules
To get started, we are going to install some node modules to get the chat room ready quickly. Ensure you have Node and NPM installed, then open your command line or terminal and run this:
The command above will install ExpressJS framework, Redis client, Bluebird to promisify the Redis client, Socket.io, Pug as view template engine, node-env-file to configure environment file (.env) and body-parser to parse body requests, especially for POST methods.
Also, we are going to use Babel to transpile our JavaScript from ES6.
Quick Note: Socket.io allows real-time communication among clients. We will use it to send event-based messages between the web clients and the server.
Setting up Redis
Create a folder lib
in your project root folder and create a file named redis.js, i.e lib/redis.js
and copy the code below:
In the code snippet above, we did the following:
- Imported the Redis, Bluebird and node-env-file modules
- Set the environment file
- Promisify the Redis module
- Created a function that returns the Redis client or an error
- Exported the client
Since the connection to the Redis server is asynchronous, there is a high chance that our Redis client might not have been created and returned before we need it, this can be disastrous! Therefore to prevent a problem, the client()
function returns a promise that either resolves with the Redis client or rejects with an error.
The client will be used to publish messages, subscribe to channels and listen for messages and to store published messages so that they are available after the broadcast.
Awesome right?!
Helper Functions
Now, we are going to declare and export some functions that we will use later during this tutorial. The reason why we are creating this helper functions, is to make our code base more readable and well organised. Our helper functions are going to help communicate with Redis to perform various actions which include
- Fetching all chat messages
- Storing messages using list data type
- Fetching all users
- Adding users using set data type
- Deleting a user
Create a file functions.js
in lib
folder i.e lib/functions.js
and add the code snippet below
In the code snippet above, we imported the Redis client function and declared five functions: fetchMessages()
, addMessage()
, fetchActiveUsers()
, addActiveUser()
and removeActiveUser()
.
Before we continue, you might want to catch up on Redis data types here. Let’s quickly take a look at what is happening in each function
fetchMessages()
: This function returns a promise that resolves with an array of messages or rejects with an error. Onceclient()
promise has been resolved, it fetches all the messages in the listmessages
using the promisified LRANGE commandlrangeAsync()
addMessage()
: This function returns a promise that resolves with the length of the list added or rejects with an error message. Onceclient()
promise has been resolved, it makes use of a promisified transaction, which queues up the RPUSH command that inserts message into the listmessages
and execute.fetchActiveUsers()
: This function returns a promise that resolves with an array of users or rejects with an error. Onceclient()
promise has been resolved, it uses the promisified SMEMBER commandsmemberAsync()
to fetch all users in the setusers
addActiveUser()
: Onceclient()
promise has been resolved, this function uses a promisified transaction which queues up the SADD command that inserts a user into the setusers
and execute. It returns a promise that resolves with the number of element added to the set or rejects with an error message. A Redis set doesn't allow repetition of elements, therefore it makes it more convenient to store unique elements like usernames.removeActiveUser()
: Onceclient()
promise has been resolved, this function uses a promisified transaction that queues up the SREM command that removes a user from the setusers
and then execute. This function returns a promise that resolves with the number of elements removed or rejects with an error message
Setting up the routes
Routes basically handle how our web app send HTTP responses to various endpoints . A route usually has a path (e.g ‘/messages’), a verb (e.g GET) and a handler function. For this tutorial, we are going to need routes for the following
- The homepage of the app
- The chat room
- Fetching all the messages
- Sending a message
- Adding a user to the chat room
- Removing a user from the chat room
Create a file routes.js
in the server
folder created earlier , i.e server/routes.js
and add the code below into it
In the code snippet above, we did the following
- Imported Express, the Redis client and our helper functions
- Created an instance of Express Router
- Created two functions that return messages and active users stored on Redis, using the
fetchMessages()
andfetchActiveUsers()
helper functions
Next, let’s create some of our app routes, paste the code snippet below into routes.js
In the code snippet above, we created and exported the routes for the following
- Displaying the homepage
- Displaying the chat room
- Returning all messages to the client
- Returning all active users to the client
Let’s create more routes that will be needed in the app, paste the code snippet below to routes.js
In the code snippet above, we created and exported the routes for the following
- Creating a user
- Deleting a user
- Creating a message
Let’s take a look at each of the routes above.
createUser
: This route handles adding a user to active users. Once a user has been added, a message announcing the new user to chat room and the updated active users are published using the Redis client. After that, the new message is then stored in Redis and a HTTP response is sent.deleteUser
: This route handles removing a user from active users. After the user is removed from the setusers
on Redis, a message announcing that the user has left the chat room and the the updated active users are published using the Redis client. Finally, the message is then stored on Redis and a HTTP response is sent.createMessage
: This route deals with messages sent by users in the chat room. Once a message is sent, it is published using the Redis client and then stored on Redis.
Setting up the server
For this tutorial, we will use Express as our Node framework. Express is a minimal and flexible framework that provides a robust set of features for web and mobile applications. Create a folder server
and create a file server.js
in it, i.e server/server.js
and copy the code snippet below into it:
In the code snippet above, we did the following
- Imported Express, bodyParser, socket.io, path, node-env-file modules
- Imported the Redis client we created in
lib/redis.js
- Imported the routes in
server/routes.js
- Set the environment file
- Created the instance of Express
- Set Pug as our view template engine and configured the path to our templates
- Configured the path of our static files (stylesheets, scripts, etc)
- Configured a middleware to use body-parser to parse the request body
Next, we are going to subscribe to channels using the Redis client and listen for messages published on those channels. Once someone sends a message or joins the chat room, we will send the message or updated users list to all users using Socket.io. Copy the code snippet below to server.js:
We subscribed to two channels: chatMessages
and activeUsers
. Whenever a message is published, an event message
is triggered. In the code snippet above, we did the following
- We declared the application endpoints
- We started the server
- We created an instance of Socket.io using the server we started
- Once Socket.io establishes connection, we used the Redis client to subscribe to
chatMessages
andactiveUsers
channels - We checked for any
message
event triggered by a published message. - We then broadcasted the message with Socket.io, using
message
event for messages fromchatMessage
channel andusers
event for messages fromactiveUsers
channel
The /
endpoint displays the homepage of the app, by rendering the index view template, while the /chat/:username
renders the chat room using the room view template. We will cover the view templates later in this tutorial.
The GET /messages
endpoint returns the history of messages shared in the chat room while GET /users
returns the list of active users stored.
The POST /user
endpoint handles adding users to active users. The POST /message
handles publishing and storing of messages sent by users. The DELETE /user
endpoint removes users from active users.
The Frontend
Remember we said something about Pug (formerly known as Jade) earlier on? Yes! We are using it as our view template engine, alongside CSS & JQuery to build the front end client for the chat room.
Create a folder public/views
views and a file in it master.pug
master.pug so that the path is public/views/master.pug
. This file will be the parent template, which other child templates will inherit. Copy the code snippet below:
When we extend this file, we can override specific areas labeled blocks. In the code snippet above, we have two blocks: content
and script
script. We will be extending the file in our child templates using those blocks. Also, Pug relies on indentation to create parent and child elements; therefore, we have to pay attention to that. You can check out pug’s documentation.
Next, we create index.pug
, the template file we called in our GET /
endpoint. It will hold the structure of the homepage. Copy the code snippet below:
In the code snippet above, we are extending master.pug
and overriding the block content to hold the structure we want. For the homepage, we are going to display a form to collect the username and submit it.
Next, let’s create the template file for the chat room, create file room.pug
and paste the code snippet below:
Here, we added the socket.io script and the script handling the room, by overriding the block script.
Next, we need to style the structure we have defined above. Create a stylesheet style.css
in public/assets/css
, so that the path is public/assets/css/style.css
and paste below into it:
Finally, we need make our username registration form and chat room work. We are going to make this possible by using jQuery; a javascript library, to make requests to the endpoints.
First, let’s create the script to handle username registration. Create a folder js
in public/assets
and create file script.js
in it, i.e public/assets/js/script.js
and paste the code snippet below
Once the user submits the form, a POST request is sent to /user
with the username. If the response status is 200, we redirect to the chat room, else, we display a message that disappears after 2 seconds.
Next, let’s power our chat room. We need to display the chat history, active users, update the message wall when another user sends a message and also update the users list when a user joins or leave. Create a file chat.js
in public/assets/js
folder and copy the code snippet below:
In the code snippet above, we did the following
- Created an instance of socket.io named
socket
- We fetched all the messages from the server and displayed it
- We fetched the list of all active users from the server and displayed it
- Send messages submitted by users to the server, either by clicking the send button or hitting enter.
- Remove a user from the active user list on the server, just before the browser tab or window is closed
- listen for messages emitted by the server and add them to the message wall using socket.io
- Listen for updated active users list and display them using socket.io
Our chat room is now ready for deployment!
Deployment
Before we deploy the application for usage, we need to quickly edit package.json in the root folder, so that we can start the app automatically, when we deploy. Add this to the file:
Finally, time to deploy! We will use a simple tool called now by Zeit. Let’s quickly install this, run this in your terminal:
Once the installation is done, navigate to the project root folder in your terminal and run the now command. If it is your first time, you will be prompted to create an account. Once you are done, run the now command again, a URL will be generated and your project files uploaded.
You can now access the simple application via the URL generated. Pretty straightforward!
Conclusion
We have been able to focus more on building our chat room, without having to worry about infrastructure. The ability to provision resources very fast, all from a single dashboard means we get to focus more on writing code. Don’t stop here, get creative and improve on the code in this tutorial, you can find the source code for this tutorial on Github, fork it and do awesome stuffs.
About Manifold
Manifold is the first independent marketplace for developer services and the easiest way to manage and share everything from JawsDB, a simple postgres database to Mailgun, an API that makes sending and receiving email dead simple.
For more info visit Manifold.co or follow us on twitter. This post is part of the Manifold Content program. Want to write for us?