WebSocket Demo Server in Node.js


In this article we would like to walk you through how we built a demo Node.js server that serves example stocks data over WebSocket. If you are new to WebSockets, you can read more in the specification.


To follow along, make sure you have Node.js and npm installed. Our preferred method is to use nvm to manage multiple versions and switch between them. You can find out more about nvm here.


You can find the code for this article on GitHub.


Setup


Choose a location on your system to store the project folder and go to that location in the command line. Create a new folder for your project and create a package.json file. Answer questions in the console to configure your package.json file or skip them to keep the defaults.


mkdir StocksWebsocketDemoServer && cd StocksWebsocketDemoServer
npm init


Open the newly created project folder in the code editor of your choice. Our package.json file looks like so at the start.


{
  "name": "stockswebsocketdemoserver",
  "version": "1.0.0",
  "description": "This is a demo server that serves example stocks data over WebSocket.",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "LOSTMOA LIMITED",
  "license": "MIT"
}



Create a WebSocket Server


For our server we will use a WebSocket library called ws. Install it with npm and save as a dependency.


npm install ws --save


Then add a server.js file and create a WebSocket server listening on port 8080.


let WebSocketServer = require('ws').Server

let wss = new WebSocketServer({port: 8080})
console.log("WebSocket server is listening on port 8080.")



Example Stocks Data


For demo purposes, we will create some fake stocks data with the help of fake-stock-market-generator library.


First we need to install the dependency.


npm install fake-stock-market-generator --save


Then we will use it to write a script in a separate file called generateStocks.js that will generate an array of stock objects and save them to a file.


let fakestockmarketgenerator = require('fake-stock-market-generator')
let fs = require("fs")

let stocks = [...Array(20).keys()].map(i => {
    return fakestockmarketgenerator.generateStockData(10)
})
fs.writeFileSync('stocks.json', JSON.stringify(stocks, null, 4));


We will add this script to our package.json file in the scripts property.


   "scripts": {
    "generate-stocks-data": "node generateStocks.js"
  }


Now we can run it from the command line.


npm run generate-stocks-data


This will create a file called stocks.json that contains an array of stock objets.


In our server.js file we will now read the stocks data and save it in a variable, so that our server can later serve that data to clients.


let WebSocketServer = require('ws').Server
let fs = require("fs")

let stocks
try {
    stocks = JSON.parse(fs.readFileSync("stocks.json"))
    console.log("Successfully loaded stocks data.")

} catch {
    throw Error("Could not load stocks data.")
}
let stockSymbols = stocks.map(stock => stock.symbol)
console.log(`Supported stock symbols: ${stockSymbols}`)

let wss = new WebSocketServer({port: 8080})
console.log("WebSocket server is listening on port 8080.")



WebSocket Demo API


Clients will be able to interact with our server with JSON text messages. They will connect to the server and subscribe to particular stock updates. To test our server as we write it, we will use Cleora - HTTP & WebSocket Client.


The full code for the server.js file described here can be found on GitHub. We will go through the main parts.



Establish a Connection


When a new connection to the server is initiated, the server will send a message listing available stock symbols.


let getConnectedEvent = () => {
    let event = {
        event: "connected",
        supportedSymbols: stockSymbols,
        message: "All stocks data is not real and is generated solely for demo purposes."
    }
    return JSON.stringify(event)
}

wss.on('connection', (ws) => {
    ws.send(getConnectedEvent())
})


To try it out, start the server in the command line.


node server.js


Then in Cleora app create a new WebSocket request. Enter your IP address and port for your local server in the URL field and open a new connection.


iPad screenshot


If everything goes well, we should see the connected message in our connection activity view. You can tap on the message to expand it and check it fully.


iPad screenshot



Receive Client Messages


We will expect two kind of messages from the client: to subscrive to specific stock updates and to unsubscribe from specific stock updates. We will first check that the message is not too long, so that we don't waste server resources parsing it, then parse the message and handle it according to the message event type. If we get a message that we don't expect, we will send an error message back to the client.


wss.on('connection', (ws) => {
    ws.send(getConnectedEvent())

    let connectionInfo = {
        stocksToWatch: []
    }
    

    ws.on('message', (message) => {
        connectionInfo.isActive = true

        if (message.length > 300) {
            ws.send(getErrorEvent("message too long"))
            return
        }

        let parsedMessage
        try {
            parsedMessage = JSON.parse(message)
        } catch {
            ws.send(getErrorEvent("invalid message"))
            return
        }

        if (parsedMessage.event === "subscribe") {
            handleSubscribe(ws, parsedMessage, connectionInfo)
        } else if (parsedMessage.event === "unsubscribe") {
            handleUnsubscribe(ws, parsedMessage, connectionInfo)
        }
    })
})



Send Updates to the Client


The server will send updates every 10 seconds if the array of stocksToWatch is not empty. It will set an interval when a client connects and clear the interval when the connection is closed.


let connectionInfo = {
    stocksToWatch: [],
    stocksUpdateCount: 0
}

ws.stocksInterval = setInterval(() => {
    if (connectionInfo.stocksToWatch.length > 0) {
        ws.send(getStocksUpdateEvent(connectionInfo))
        connectionInfo.stocksUpdateCount += 1
    }
}, 10000)

ws.on('close', () => {
    clearInterval(ws.stocksInterval)
})


To test the updates we will subscribe to IET and ZHT stocks by sending a message that looks like this.


{
  "event": "subscribe",
  "stocks": ["IET", "ZHT"]
}


We can see that the updates for those stocks are now coming from the server every 10 seconds.


iPad screenshot


To unsubscribe from the updates we can send this message.


{
  "event": "unsubscribe",
  "stocks": ["IET", "ZHT"]
}


After we send the unsubscribe message, we should stop receiving the updates.



Monitor the Connection Status


To make sure that we don't keep an inactive connection open for too long our server will send ping frames every 15 seconds. If the client doesn't respond with a pong frame, the connection will be closed on the next iteration of the interval.


ws.pingInterval = setInterval(() => {
    if (!connectionInfo.isActive) {
        disconnect(ws, "connection inactive")
    } else {
        connectionInfo.isActive = false
        ws.ping()
    }
}, 15000)

ws.on('pong', () => {
    connectionInfo.isActive = true
});

ws.on('close', () => {
    clearInterval(ws.pingInterval)
    clearInterval(ws.stocksInterval)
})


Because this is just a demo server that anyone can connect to, we don't want to keep any one connection open for more than 5 minutes to avoid a big load on the server. To achieve this we will set a connection timeout.


ws.connectionTimeout = setTimeout(() => {
    disconnect(ws, "connection time exceeds 5 minutes")
}, 300000)

ws.on('close', () => {
    clearInterval(ws.pingInterval)
    clearInterval(ws.stocksInterval)
    clearTimeout(ws.connectionTimeout)
})



Stocks WebSocket Demo Server Project


The Stocks WebSocket Demo Server Project is an open source project hosted on GitHub. It's going to be extended and improved in the future. Don't hesitate to contribute by submitting pull requests or opening issues.


The demo server is accesible on wss://stocks.websocket.demo.cleora.app. You can interact with it using Cleora or any other WebSocket client.