FullStack Labs

Please Upgrade Your Browser.

Unfortunately, Internet Explorer is an outdated browser and we do not currently support it. To have the best browsing experience, please upgrade to Microsoft Edge, Google Chrome or Safari.
Upgrade

Develop a Chat Application Using React, Express and Socket.IO

Written by 
David Tobar
,
Software Engineer
Develop a Chat Application Using React, Express and Socket.IO
blog post background
Jira Time Tracking
Must-Have Productivity Tools for Working from Home
Hybrid Business Analyst / Project Manager Role in Agile Software-Development Projects
Node.js Developer Salaries
Comparing Hourly Rates Between Software Consultancies
React Native App Of The Week: Honey

Creating new applications using React and Node.js always requires a certain amount of work before you actually start working on the final solution. In this tutorial you will find everything you need to start from scratch.

Develop a Chat Application Using React, Express and Socket.IO

Start with the basics

*Note: Prior to starting working with this tutorial you must have at least Node.js 10 or higher installed on your machine. You can find the installation guide here 

Installing Node.js dependencies

First, we're going to use Express as the API that will handle every request made by the front-end:

	
  npm --save express
	

Now that we have installed our core library we can proceed to create the Node file that will execute all the logic of our application. Create a server.js File in the root directory of your back-end project:

Inside server.js place the following code:

-- CODE language-jsx keep-markup --
var app = require('express')();
var http = require('http').createServer(app);
const PORT = 8080;

http.listen(PORT, () => {
    console.log(`listening on *:${PORT}`);

});

Now that you have placed the code inside server.js, let’s quickly make sure everything is working by running the following command:

-- CODE language-bash keep-markup --
node server.js

If everything is in order, the message “listening on *:8080” should appear in the console.

Socket.IO

Now we can proceed to install the library which can handle the web sockets connections:

-- CODE language-bash keep-markup --
npm i --save socket.io

Let’s create our first socket listener in the server.js file:

-- CODE language-jsx keep-markup --
var app = require('express')();
var http = require('http').createServer(app);
const PORT = 8080;
var io = require('socket.io')(http);
http.listen(PORT, () => {
    console.log(`listening on *:${PORT}`);

});

io.on('connection', (socket) => { /* socket object may be used to send specific messages to the new connected client */

    console.log('new client connected');

});

Creating the Front End

The first step to create the front end of our applications will be to initialize the React application. You can easily do this with the command:

-- CODE language-bash keep-markup --
npx create-react-app my-app

Now that the code base is initialized, we can proceed to install the Socket.IO library for our front end. Just use the client library of socket.io with:

-- CODE language-bash keep-markup --
npm i socket.io-client

Connecting the client with the server

If this is your first time using Socket.IO, this part will be exciting since we are enabling real-time communication between a single client and our back end using web sockets. In the ~/App.js include the Socket.IO client and create a variable to store the socket object like this:

-- CODE language-jsx keep-markup --
import React from 'react';
import logo from './logo.svg';
import './App.scss';
import socketClient  from "socket.io-client";

const SERVER = "http://127.0.0.1:8080";

function App() {
    var socket = socketClient (SERVER);

    return (
        <div className="App">

            <header className="App-header">

                <img src={logo} className="App-logo" alt="logo" />

                <p>
        
                Edit <code>src/App.js</code> and save to reload.
  
               </p>

               <a
                   className="App-link"
                   href="https://reactjs.org"
                   target="_blank"
                   rel="noopener noreferrer"
                >

                   Learn React

              </a>

            </header>

        </div>
    );

}

export default App;

Now that we have our socket variable, we can start listening to events emitted by our back end. In order to be notified when the client is connected we have to tweak our previous code in the server.js file:

-- CODE language-jsx keep-markup --
var app = require('express')();

var http = require('http').createServer(app);

const PORT = 8080;

var io = require('socket.io')(http);

const STATIC_CHANNELS = ['global_notifications', 'global_chat'];

http.listen(PORT, () => {
    console.log(`listening on *:${PORT}`);

});

io.on('connection', (socket) => { /* socket object may be used to send specific messages to the new connected client */
    console.log('new client connected');
    socket.emit('connection', null);

});

With the  socket.emit  function, custom events can be sent from the back end to the front end through the newly established socket connection. This method will only send messages between the specific client that was recently connected. Sending messages to all the clients connected will be explained later. 

In order to receive those notifications from the back end, we need to listen for the events created there. For example, we are emitting the connection event to the client as soon it opens a new connection, so we have to put the same label in our front to execute some code when this happens:

-- CODE language-jsx keep-markup --
var socket = socketClient (SERVER);
    socket.on('connection', () => {
        console.log(`I'm connected with the back-end`);

});

In the browser it should look like this:

Building the chat

With the installation complete, we can focus on building the UI of our application and the logic behind it. 

In order to create a standalone components, we have to create a folder called “chat” with the following structure: 

Chat.js

-- CODE language-jsx keep-markup --
import React from 'react';
import { ChannelList } from './ChannelList';
import './chat.scss';

export class Chat extends React.Component {

        state = {
            channels: [{ id: 1, name: 'first', participants: 10 }]
        }

        render() {
            return (
                <div className='chat-app'>

                    <ChannelList channels={this.state.channels} />

                </div>
            );
        }

}

ChannelList.js

-- CODE language-jsx keep-markup --
import React from 'react';
import { Channel } from './Channel';

export class ChannelList extends React.Component {

    render() {
        
        let list = `There is no channels to show`;
        if (this.props.channels) {
            list = this.props.channels.map(c => <Channel key={c.id} id={c.id} name={c.name} participants={c.participants} />);
        }
        return (
            <div className='channel-list'>

                {list}

            </div>);
        }

}

Channel.js

-- CODE language-jsx keep-markup --
import React from 'react';

export class Channel extends React.Component {

    render() {
        return (
            <div className='channel-item'>

                <div>{this.props.name}</div>

                <span>{this.props.participants}</span>

            </div>
        )
    }

}

chat.scss

-- CODE language-scss keep-markup --
.chat-app {
    width: 100%;
    height: 100%;
    display: flex;

    .channel-list {
        width: 20%;
        border: 1px solid rgb(224, 224, 224);
        margin: 10px;
        height: calc(100% - 22px);
    }
    
    .channel-item {
        border-bottom: 1px solid rgb(224, 224, 224);
        padding: 10px;

        div {
            font-weight: bold;
        }

        span {
            font-size: 10px;
        }

        &:hover {
            background-color: rgb(224, 224, 224);
        }
    }

}

To see the chat as a full window component, you might want to stylize the root element to fill the whole screen. To do so, you will have to modify the index.css file and add this:

 -- CODE language-css keep-markup --
html, body {

    margin: 0;

    height: 100%;

}

#root {

    width: 100%;

    height: 100%;

}

At this point the application should look something like this

Now that we have an interface structure, we might want to define the messages panel. For this, we have to create a new component called MessagesPane.js  and Message.js.


Message.js

-- CODE language-jsx keep-markup --
import React from 'react';

export class Message extends React.Component {

    render() {
        return (
            <div className='message-item'>

                <div><b>{this.props.senderName}</b></div>

                <span>{this.props.text}</span>

            </div>
        )
    }

}

MeesagesPanel.js

-- CODE language-jsx keep-markup --
import React from 'react';
import { Message } from './Message';

export class MessagesPanel extends React.Component {

    render() {

        let list = <div className="no-content-message">There is no messages to show</div>;
        if (this.props.channel && this.props.channel.messages) {
            list = this.props.channel.messages.map(m => <Message key={m.id} id={m.id} senderName={m.senderName} text={m.text} />);
        }
        return (
            <div className='messages-panel'>

                <div className="meesages-list">{list}</div>

                <div className="messages-input">

                    <input type="text" />

                    <button>Send</button>

                </div>

            </div>);
        }

}

In order to adjust the styles of the application, we have to make some changes to our chat.scss file. It should look like this:

-- CODE language-scss keep-markup --
.chat-app {
    width: 100%;
    height: 100%;
    display: flex;

    .no-content-message {
        color: #cccccc;
        font-style: italic;
        font-size: 20px;
        text-align: center;
        margin: 20px;
    }

    .channel-list {
        width: calc(20% - 12px);
        border: 1px solid rgb(224, 224, 224);
        margin: 10px;
        margin-right: 0;
        border-right: none;
        height: calc(100% - 22px);
    }

    .channel-item {
        border-bottom: 1px solid rgb(224, 224, 224);
        padding: 10px;

        div {
            font-weight: bold;
        }

        span {
            font-size: 10px;
        }

       
        &:hover {
            background-color: rgb(224, 224, 224);
            cursor: pointer;
        }
    }

    .messages-panel {
        width: calc(80% - 12px);
        border: 1px solid rgb(224, 224, 224);
        margin: 10px;
        margin-left: 0;
        height: calc(100% - 22px);
        display: flex;
        flex-direction: column;
        align-items: flex-start;

        .meesages-list {
            align-self: stretch;
            height: 100%;
        }

        .messages-input {
            width: 100%;
            height: 40px;
            border-top: 1px solid rgb(224, 224, 224);
            background-color: #f0f0f0;
            display: flex;

            input {
                margin: auto;
                height: 20px;
               width: 100%;
                margin-left: 15px;
                border-radius: 15px;
                border: 1px solid rgb(224, 224, 224);

                &:focus {
                    border-radius: 15px;
                    border: 2px solid #66a6ff;
                    outline: none;
                 }
            }

            button {
                width: 60px;
                margin: auto 10px;
                background-color: #0e62da;
                color: white;
                border: 1px solid;
                border-radius: 10px;
                padding: 5px 13px;

                &:hover {
                    cursor: pointer;
                    background-color: #66a6ff;
                }
            }
        }
    }

}

After these changes are made, we need to introduce the new panel into the Chat.js file:

Developing the logic

The interface has its basic form. Now it’s time to start developing some logic to send and receive messages. 

Fetch the channels

We need to retrieve the current channel's information. For this, we have to fetch this from the back end. In server.js, we can add:

-- CODE language-jsx keep-markup --
app.get('/getChannels', (req, res) => {
    res.json({
        channels: STATIC_CHANNELS
    })

});

For the front, modify the Chat.js file and add this function:

-- CODE language-jsx keep-markup --
componentDidMount() {
    this.loadChannels();

}


loadChannels = async () => {
    fetch('http://localhost:8080/getChannels').then(async response => {
        let data = await response.json();
            this.setState({ channels: data.channels });
    })

}

With the code above the information for the channels should be displayed in the application like this:

You are now connected with the back end via websocket and http. For real time communication, we have to define some listeners and emit some events in order to update the information of every channel. Selecting a channel should trigger an event that the back end will handle.

The front end needs to handle these events as well. To capture the events we have to use the same event name that the back end emits and vice versa. When the client hits the channel, an event must be sent to the back end to calculate the number of participants and broadcast to all the current sockets that a new user entered the channel.

Chat.js

-- CODE language-jsx keep-markup --
handleChannelSelect = id => {
        this.socket.emit('channel-join', id, ack => {
        });
    }
    
    render() {

        return (
            <div className='chat-app'>

                <ChannelList channels={this.state.channels} onSelectChannel={this.handleChannelSelect} />

                <MessagesPanel />

            </div>
        );

}

server.js

	
io.on('connection', (socket) => { // socket object may be used to send specific messages to the new connected client
    console.log('new client connected');
    socket.emit('connection', null);
    socket.on('channel-join', id => {
        console.log('channel join', id);
        STATIC_CHANNELS.forEach(c => {
            if (c.id === id) {
                if (c.sockets.indexOf(socket.id) == (-1)) {
                    c.sockets.push(socket.id);
                    c.participants++;
                    io.emit('channel', c);
                }
            } else {
                let index = c.sockets.indexOf(socket.id);
                if (index != (-1)) {
                    c.sockets.splice(index, 1);
                    c.participants--;
                    io.emit('channel', c);
                }
            }
        });
 
        return id;
    })
});
	

With this logic implemented, the application now captures the events emitted from the client and the server. 


Sending Messages

It’s time to start sending some specific messages over the websocket. To accomplish this, we have to capture the information in our textbox and then submit it by clicking the send button. When the click event is captured, we have to make sure that we can send all the information related to the message. In this case the information will be:

  • senderName: The id of the socket sending the message.
  • id: The id of the message which is going to be the current timestamp.
  • text: The text captured in the input.
  • channel_id: The id of the channel where the message was sent to.

Chat.js

-- CODE language-jsx keep-markup --
configureSocket = () => {

  var socket = socketClient(SERVER);
  socket.on('connection', () => {
    if (this.state.channel) {
      this.handleChannelSelect(this.state.channel.id);
    }
  });

  socket.on('channel', channel => {
    
    let channels = this.state.channels;
      channels.forEach(c => {
        if (c.id === channel.id) {
          c.participants = channel.participants;
        }
  });

  this.setState({ channels });
});

socket.on('message', message => {
  let channels = this.state.channels
    channels.forEach(c => {
      if (c.id === message.channel_id) {
        if (!c.messages) {
          c.messages = [message];
        } else {
          c.messages.push(message);
        }
      }
    });
    this.setState({ channels });
  });
  this.socket = socket;
}

handleSendMessage = (channel_id, text) => {
  this.socket.emit('send-message', { channel_id, text, senderName: this.socket.id, id: Date.now() });
}

render() {
  return (
    <div className='chat-app'>

      <ChannelList channels={this.state.channels} onSelectChannel={this.handleChannelSelect} />
      
      <MessagesPanel onSendMessage={this.handleSendMessage} channel={this.state.channel} />
    
    </div>
  );

}

For the back the integration is simple, as we only have to broadcast the messages received.

-- CODE language-jsx keep-markup --
socket.on('send-message', message => {
    io.emit('message', message);

});

The final result should look like this:

The full code can be found here:

Disclaimer

Here at FullStack Labs, our UI/UX designers, React developers, and Node.js developers all follow a playbook and apply coding best practices such as those found here. The previous guide was intended only to explain a simple way to communicate between the front end and the back end of a single application and makes no claim as to best practices around React nor Node development.

David Tobar
Written by
David Tobar
David Tobar

I am a Senior Software Engineer who loves developing web and mobile applications. In addition to my work with FullStack Labs, I am currently working on personal projects in order to learn new development techniques. I like to always be learning the latest technologies and standards of the market to be able to create the best and most functional applications that end users can enjoy. My favorite stack is the Full Stack Javascript. When I'm not coding, I love to play video games!

FullStack Labs Icon

Let's Talk!

We’d love to learn more about your project. Contact us below for a free consultation with our CEO.
Projects start at $50,000.

company name
name
email
phone
Type of project
Reason for contact
How did you hear about us?
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.