Getting Started with WebSockets and Node & React

WebSockets are an efficient way to deliver content to your web application. With the typical HTTP protocol data is requested from your web client application from your web server and it will then reply with the content. With WebSockets a continuous two-way interactive session is created between the client application and the server. Once a connection is established between the client and the server though web sockets, data can be transferred to both the client and the server in a continuous event-driven manner without the client to repeatedly poll for the data.

Here we will build a simple application that will echo the messages from a web client application. We will use Node.js and the express middleware to build the server. The frontend will be a simple React application. I will use socket.io framework here for adding the WebSockets functionality.

Backend

Setup

Let’s get our project set up for the backend:

mkdir -p websockets_testdrive/backend

Init the NPM project – simply use the default values suggested

cd websockets_testdrive/backend
npm init

Now let’s add the dependencies for express and socket.io libraries

npm install -s express socket.io

Code

We will implement the server in a file named server.js (double check that the entry for "main" in package.json is pointing to it).

First step is to import our libraries and have the server listening to the port 3000 – you will see code here for enabling it for CORS:

var app = require('express')()
var http = require('http').createServer(app)
var io = require('socket.io')(http, {
    cors: {
      origin: "*",
      methods: ["GET", "POST"]
    }
  })
const cors = require('cors')

app.use(cors())

http.listen(port=3000, () => {
    console.log('Listening on *:%i', port)
})

Running a simple npm run start will simple let the server listen on port 3000. With the following lines added to the server.js file we will implement a simple protocol. Whenever the server receives a message-event event through the web socket, the server will log out the contents of the event and reply to everyone on web socket with the same content as well. This is wrapped, however, in a message-response-event:

io.on('connection', (socket) => {
    socket.on('message-event:', (msg) => {
        console.log('Received a message-event: %s', msg)
        io.emit('message-response-event', msg)
    })
})

Our code is done on the server now. We run it with npm run start and can focus our efforts now to the frontend.

Enable CORS on socket.io

In the code above you will find the settings to be made to enable CORS on the server for HTTP and WebSocket traffic. For the socket.io library, this needs to be set explicitly as in:

var io = require('socket.io')(http, {
    cors: {
      origin: "*",
      methods: ["GET", "POST"]
    }
  })

Frontend

We will use the create-react-app script to get our React frontend project setup done here:

cd websockets_testdrive
npx create-react-app frontend

This will take a while for the files to be created and the dependencies resolved. Now add the socket.io-client library and give the initial app a try:

cd frontend
npm install socket.io-client -s
npm run start

Messenger Component

We will implement the WebSocket client in a Messenger React component. The component will list all the messages sent over by the client above and a textbox allows the user to send a message using the WebSocket connection:

import React, { useEffect, useState } from 'react'
import io from 'socket.io-client'

function Messenger() {
    const [receivedMessages, setReceivedMessages] = useState([])
    const [message, setMessage] = useState('Hello')

    const handleSubmit = (e) => {
        e.preventDefault()
        setMessage('')
    }
    return (
        <div>
            <div>
                <ul>
                    {
                        receivedMessages.map( (item, index) => (
                            <li key={index}>{item}</li>
                        ))
                    }
                </ul>
            </div>

            <form onSubmit={handleSubmit}>
                <label htmlFor="txtMessage">
                    Your message:
                </label>
                <input
                    id="txtMessage"
                    value={message}
                    onChange={e => setMessage(e.target.value)}
                />
                <button>
                    Send
                </button>
            </form>


        </div>
    )
}

export default Messenger

There is nothing too special in the component above. It is a React function based component with some React Hooks for showing all the receivedMessages and keeping track of the inputbox’ content in the message hook.

Now let’s integrate the component into the React starter component in the App.js file:

import './App.css'
import Messenger from './Messenger'

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <Messenger/>
      </header>
    </div>
  );
}

export default App

We can now start the frontend with

npm run start

and continue our development on the WebSocket functionality in the Messenger.js file. We will need to keep track of the WebSocket with a useRef()hook.

const socket = useRef()

The useRef hook will hold an instance of the WebSocket over the complete lifetime of the socket.

Using the useEffect() hook, we initialise the WebSocket connection to the server and register a subscriber whenever a message-response-event is triggered by the server:

useEffect(() => {
        if (!socket.current) {
            socket.current = io.connect('localhost:3000')
            socket.current.on('message-response-event', msg => {
                console.log('received message-response-event: %s', msg)
                setReceivedMessages(receivedMessages => [...receivedMessages, msg])
            })
        }
        
    }, [])

Finally we extend the handleSubmit code by sending a message-event with the contents of message through the WebSocket:

    const handleSubmit = (e) => {
        e.preventDefault()
        let s = socket.current
        s.emit("message-event", message)
        setMessage('')
    }

That’ll be it in the frontend and the backend. Now you can test the application on the web browser. Open up a second browser tab and see how the changes from the one session are shown in both browser tabs.

Further Reading

This is a simple setup for now. All the changes sent over the WebSocket are using the same channel and they broadcast the events through it. There are different communication patterns possible to support different scenarios. Read about it in the Broadcasting section in the socket.io documentation.

Open items

  • Make code available on GitHub
  • Add screenshots
  • Proof read it

Leave a comment