I have a setup similar to this one:
var WebSocketServer = require(\"ws\").Server,
express = require(\"express\"),
http = require(\"http\"),
app =
Update: As of 06/12/2020 this package no longer works with newer versions of Express. Suggestion is to use the ws or socket.io packages.
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)
}
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();
}
});
You'll want to use the path
option:
var wss = new WebSocketServer({server: server, path: "/hereIsWS"});
See full documentation here
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;
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! :)