How to use Websocket with a simple Express API?

Brief description of technology



Websocket is a communication protocol over a TCP connection, designed to exchange messages between a browser and a web server in real time.



The client and server use a protocol similar to HTTP to establish a WebSocket connection. The client makes a special HTTP request to which the server responds in a specific way.



Remarks



Although the new requests and responses are "similar" to the HTTP requests and responses, they are not. For example, the request has a body, but the headers do not have the Content-Length field (which violates HTTP conventions). You can read more about this on Wikipedia .



One of the main advantages of the technology is its simplicity. There are only 4 events on the client and server to process:



  1. connection
  2. error
  3. message
  4. close


Why Websocket?



In addition to ws, there are two other methods of continuous data transmission: Server-Sent Events (SSE) and Long Polling.



Let's compare the mechanisms of continuous communication between the server and the client, and also draw conclusions about why it is worth (or not worth) to use a websocket.



Websocket sse long pooling
protocol websocket (ws, or wss) HTTP (S) HTTP (S)
speed high low low
directionality of data streams bidirectional unidirectional bidirectional
additionally transfer of binary data, there is

no support for some old browsers
automatic reconnection when the connection is broken


One of the main advantages of ws technology is data transfer speed. SSE and LP use the HTTP (S) protocol and work something like this:



  1. Making a request for changes;
  2. If changes appear on the server, the server sends them;
  3. , .


:



  1. .
  2. , HTTP(S).
  3. , .


api.



const http = require("http");
const express = require( "express");
const WebSocket = require( "ws");

const app = express();

const server = http.createServer(app);

const webSocketServer = new WebSocket.Server({ server });

webSocketServer.on('connection', ws => {
   ws.on('message', m => {
webSocketServer.clients.forEach(client => client.send(m));
   });

   ws.on("error", e => ws.send(e));

   ws.send('Hi there, I am a WebSocket server');
});

server.listen(8999, () => console.log("Server started"))


What's going on here?



To create a server supporting ws, we create a regular http server, and then bind a server to it when creating a websocket.



The β€œon” function helps to manage websocket events. The most notable event is the message event, so let's take a closer look at it.



Here the function receives the parameter m - the message, that is, what the user sent. Thus, we can send a string from the client and process it on the server. In this case, the server simply forwards this message to everyone connected to the websocket server. The clients array of the webSocketServer object contains all connections to the server. The ws object only stores one connection at a time.



Comment



You shouldn't use this approach in a real application. If the api is described in this way, the server cannot distinguish one request from another. How you can build a websocket-based api will be written later.



The interaction with the server on the client will look like this:



export const wsConnection = new WebSocket("ws://localhost:8999");
wsConnection.onopen = function() {
    alert(" .");
};

wsConnection.onclose = function(event) {
    if (event.wasClean) {
        alert('  ');
    } else {
        alert(' '); // , ""  
    }
    alert(': ' + event.code + ' : ' + event.reason);
};

wsConnection.onerror = function(error) {
    alert(" " + error.message);
};

export const wsSend = function(data) {
// readyState - true,   
    if(!wsConnection.readyState){
        setTimeout(function (){
            wsSend(data);
        },100);
    } else {
        wsConnection.send(data);
    }
};


Websocket based API



Unlike REST API, where requests are distributed across different urls, Websocket API has only one url. In order to build a full-fledged API based on websockets, you need to teach the system to distinguish one request from another. This can be implemented as follows:



1) From the client, we will transmit requests in the form of a json string, which we will parse on the server:



const sendMessage = (message) => conn.send(JSON.stringify({ event: "chat-message", payload: { userName, message }}));


2) On the server, we parse the string and select the event field - the type of request. Let's write down the corresponding answer for each type:



const dispatchEvent = (message, ws) => {
   const json = JSON.parse(message);
   switch (json.event) {
       case "chat-message": webSocketServer.clients.forEach(client => client.send(message));
       default: ws.send((new Error("Wrong query")).message);
   }
}


Thus, we can send different requests to the server and process the response depending on the request.



Conclusion



If you were given the task of making an API and you found out that the customer is not interested in supporting old browsers, then a WebSocket-based API is an excellent choice. For your convenience, we have prepared the code for the client and server parts at the link .



All Articles