How to setup route for websocket server in express?

Javascriptnode.jsExpressWebsocket

Javascript Problem Overview


I have a setup similar to this one:

var WebSocketServer = require("ws").Server,
    express = require("express"),
    http = require("http"),
    app = express(),
    server = http.createServer(app);

app.post("/login", login);
app.get("/...", callSomething);
// ...

server.listen(8000);


var wss = new WebSocketServer({server: server});

wss.on("connection", function(ws){
   // ...
});

I would like to put the WebSocketServer under a specific path which may for instance be "...com/whatever". The question is how can I set the path? Is it possible?

Javascript Solutions


Solution 1 - Javascript

You'll want to use the path option:

var wss = new WebSocketServer({server: server, path: "/hereIsWS"});

See full documentation here

Solution 2 - Javascript

Use express-ws: https://www.npmjs.com/package/express-ws

Installation:

npm i express-ws -S

HTTP server example:

const express = require('express')
const enableWs = require('express-ws')

const app = express()
enableWs(app)

app.ws('/echo', (ws, req) => {
    ws.on('message', msg => {
        ws.send(msg)
    })

    ws.on('close', () => {
        console.log('WebSocket was closed')
    })
})

app.listen(80)

HTTPS server example:

NOTICE I strongly recommend making such features as HTTPS, compression and caching using an intermediate server between NodeJS and Internet, for example Nginx, it works much more efficiently and its configuration will be easier to change in the future

const https 	= require('https')
const fs 		= require('fs')
const express 	= require('express')
const expressWs = require('express-ws')

const serverOptions = {
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem')
}

const app 		= express()
const server 	= https.createServer(serverOptions, app)

expressWs(app, server)

app.ws('/echo', (ws, req) => {
    ws.on('message', msg => {
        ws.send(msg)
    })

    ws.on('close', () => {
        console.log('WebSocket was closed')
    })
})

server.listen(443)

Browser client example:

// wss: protocol is equivalent of https: 
// ws:  protocol is equivalent of http:
// You ALWAYS need to provide absolute address
// I mean, you can't just use relative path like /echo
const socketProtocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:')
const echoSocketUrl = socketProtocol + '//' + window.location.hostname + '/echo/'
const socket = new WebSocket(echoSocketUrl);

socket.onopen = () => {
  socket.send('Here\'s some text that the server is urgently awaiting!'); 
}

socket.onmessage = e => {
  console.log('Message from server:', event.data)
}

Solution 3 - Javascript

UPDATE Paths are valid in the ws server options.

interface ServerOptions {
        host?: string;
        port?: number;
        backlog?: number;
        server?: http.Server | https.Server;
        verifyClient?: VerifyClientCallbackAsync | VerifyClientCallbackSync;
        handleProtocols?: any;
        path?: string;
        noServer?: boolean;
        clientTracking?: boolean;
        perMessageDeflate?: boolean | PerMessageDeflateOptions;
        maxPayload?: number;
    }

The accepted answer is no longer valid and will throw Frame Header Invalid errors. Pull Request #885.

WS Paths were removed as Lpinca puts it:

> The problem here is that each WebSocketServer adds a new listener for > the upgrade event on the HTTP server and when that event is emitted, > handleUpgrade is called on all servers.

Here is the work around:

const wss1 = new WebSocket.Server({ noServer: true });
const wss2 = new WebSocket.Server({ noServer: true });
const server = http.createServer();

server.on('upgrade', (request, socket, head) => {
  const pathname = url.parse(request.url).pathname;

  if (pathname === '/foo') {
    wss1.handleUpgrade(request, socket, head, (ws) => {
      wss1.emit('connection', ws);
    });
  } else if (pathname === '/bar') {
    wss2.handleUpgrade(request, socket, head, (ws) => {
      wss2.emit('connection', ws);
    });
  } else {
    socket.destroy();
  }
});

Solution 4 - Javascript

you could use this simple idea by putting incoming socket requests as a middleware, which I found to be pretty useful

in your app.js

const server = http.createServer(app)
const WebSocket = require('ws');
const ws = new WebSocket.Server({server});

now put middleware there

app.use(function (req, res, next) {
    req.ws = ws;
    return next();
});

or, which obviously is a bit simpler, this instead:

app.ws=ws;

now your ws construct is available in your routers, for example:

// main user dashboard GET
router.get('/', async function(req, res) {
    
        let ws = req.ws

        ws.once('connection', function connection(wss) {
            wss.on('message', function incoming(message) {
                console.log('received: %s', message);
            });

            wss.send(JSON.stringify('it works! Yeeee! :))' ));
        });
});

or if you attached it to your app by app.ws:

// main user dashboard GET
router.get('/', async function(req, res) {
    req.app.ws.once('connection', (wss) => {
            console.log('connected:', req.app.ws.clients.size)
        });
});

pay very close attention to usage of "ws.once", not "ws.on", or you'll get multiple connections at new instances of websocket.server on each request.

Cheers! :)

Solution 5 - Javascript

To build upon Ivan Kolyhalov's approach, it's possible to access the WebSocketServer from any endpoint by assigning it (or any of its properties) to app.locals. Therefore, you only have to manage handling connections to the WebSocketServer in server.js.

In the code below we assign the clients property of the WebSocketServer to app.locals, which allows us to broadcast/push a custom message to all connected clients simply by making an HTTP request to the routed endpoints.

server.js

const { createServer } = require("http");
const express = require("express");
const WebSocket = require("ws");

const app = express();
app.use(express.json({ extended: false }));
app.use("/api/pets", require("./routes/api/pets"));

const port = process.env.PORT || 5000;
const server = createServer(app);
server.listen(port, () => console.info(`Server running on port: ${port}`));

const webSocketServer = new WebSocket.Server({ server });
webSocketServer.on("connection", (webSocket) => {

    console.info("Total connected clients:", webSocketServer.clients.size);

    app.locals.clients = webSocketServer.clients;
});

./routes/api/pets.js

const router = require("express").Router();
const WebSocket = require("ws");

const broadcast = (clients, message) => {

    clients.forEach((client) => {

        if (client.readyState === WebSocket.OPEN) {

            client.send(message);
        }
    });
};

router.get("/dog", (req, res) => {
    
    broadcast(req.app.locals.clients, "Bark!");

    return res.sendStatus(200);
});

router.get("/cat", (req, res) => {
    
    broadcast(req.app.locals.clients, "Meow!");

    return res.sendStatus(200);
});

module.exports = router;

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Question0101View Question on Stackoverflow
Solution 1 - JavascriptAaron DufourView Answer on Stackoverflow
Solution 2 - JavascriptujeenatorView Answer on Stackoverflow
Solution 3 - JavascriptQuesofatView Answer on Stackoverflow
Solution 4 - JavascriptIvan KolyhalovView Answer on Stackoverflow
Solution 5 - JavascriptChunky ChunkView Answer on Stackoverflow