Building a Chat App with Express, React and Socket.io with GIFs Support

In this tutorial, we are building a real-time chat app with Node.js/Express backend and React/Bootstrap frontend. We’re using Socket.io with Express to handle incoming socket connections and emit messages to connected clients. We’re using Webpack4 to transform and bundle our frontend assets. Let’s start by initializing a new project with npm init and add these packages to dependencies.

Also, add these packages to devDependencies.

Add these to your scripts

We’re using nodemon to monitor changes in our server.js file and automatically restart our node server. You can install it globally by running

$ npm install -g nodemon

Setting up Socket.io Server

Now that we’ve installed dependencies and setup run scripts, let’s quickly dive into writing our application’s backend that will handle incoming HTTP requests and WebSocket connections. Create a server.js file in your project directory.

We are using Socket.io in conjunction with Express. We’are passing our HTTP server to socket.io and listen to incoming HTTP requests and socket connections. Later on, we’ll setup Webpack to output bundled assets to dist directory which will be accessible through /assets route. Create an index.html file in our project directory. We’ve already configured our web server to serve it against / route.

This is how our index.html file looks like. We’re bootstrapping CSS and Javascript bundle and use #root to mount our React app.

Webpack Configuration

Now we’ll configure Webpack to output transformed bundles to dist directory. Since we’re using ES6, JSX and Sass, we need to pipe our code through a few loaders to transform them into browser ready CSS and JS code. We’re using css-loader and sass-loader to transform SASS to CSS and then extract to a dedicated file using MiniCssExtractPlugin. We’re using babel-loader to transform ES6 and JSX to Javascript. We’re also applying url-loader and file-loader to images and fonts to extract them to  dist/fonts directory.

React App

This is how our app will look like and function at the end of this tutorial.

We’ve configured Webpack to use src/App.js as an entry point.  Let’s create it and write our first component. We’re rendering Navbar and Chat component in our App component.

Navbar is a default Bootstrap4 navbar.

Now let’s create a  Chat component.

We’re relying on localStorage to store username and user ID. We’re generating a user ID and storing it in browser local storage.

generateUID() method will generate a random string, use it to set uid key on local storage and return it. We’ll use it to identify users accessing chat app from different browser tabs. We’re also storing and retrieving username from local storage. If it doesn’t exist, we’ll ask user to enter a username to join chat room. We’ll render EnterChat component if chat_ready state is false. We’re initially setting it to false and later on updating it in initChat() method.

We’ll render an input form in EnterChat component. onSubmit() it will update Chat component username state.

In  componentDidMount() method, we’re validating username state.  If it exists, we’ll call initChat() method.

setUsername()  will update username state and call  initChat()  method. initChat() will initialize WebSockets and register event listeners. We’re also passing username and uid as query when initiating our WebSocket. On the backend, we’ll listen to incoming connections and retrieve username and uid from query. Update your server.js file and add this code.

On a new connection, we’ll create a user with username, uid, and socket_id. If uid already exist on users object, we’ll add sockets to the existing user otherwise will create a new user.

On disconnect, we’ll retrieve disconnected socket id and pass it to removeSocket()  method.

If a user has more than one connected socket, we’ll remove the disconnected socket. If it’s the only one, we’ll remove the user from users object. We’ll emit  updateUsersList event on socket connect and disconnect events and listen to it on our frontend.

Now that we’ve set up our Chat component, let’s move onto Users and Messages component. We’re passing users as props to Users component.

When we’ll receive new users through sockets, updated user prop will be passed to our Users component and update state through getDerivedStateFromProps method.

Let’s move on to Messages component.

In our Messages component, we’re rendering messages passed from chat component. User can toggle between Gif and Text Chat input option. We’ve separate components for handling both types of input. We’re passing sendMessage prop that we received from Chat component to both our Input components. On invoke, it will emit message event to the socket server, which will broadcast message to all connected clients expect sender. We’re assigning a dynamic height to div.message to leave space for GifBox and Chatbox input components below messages.

ChatBox is a simple text input component where a user will enter a message and it will be broadcasted to all connected clients.

GifBox will search GIFs using Giphy API.

In getGIFs()  method, we’ll call Giphy API to fetch GIFs. Make sure you add your own API key to axios params. registerScrollEvent() method will register a scroll event that will call API again with a new offset when users scroll down to the bottom of the GIF results. We’ll remove scroll event by calling removeScrollEvent() when component unmounts. When a user clicks on the share button, it will emit a send message event with gif URL.

I’ve created a repository for you. Clone it and run.

Open 127.0.0.1:8989 in your browser to use the app. If you’ve any issues or want to ask something, leave a comment and I’ll try to help you.

 

Share this post

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.