The Goal:
Let's say we have a marketplace full of shops. I'm creating a specific page localhost:3000/livePurchases/:storeId for a shop owner to monitor live notifications of when they got a new purchase.
alert('you received a new purchase') should be triggered by a WebSocket when an item is purchased.
The Problem:
I need to set up WebSockets on my express server so that the websocket can be triggered somewhere in my code different from where the websocket server was set up. But I don't understand how to do this.
The route /newPurchase/:storeId would be requested by the browser of a customer after they successfully purchase an item. The websocket should send a message within the code of the route "/newPurchase/:storeId" (backend) to the websocket on "/livePurchases/:storeId" (frontend) where the shop owner can monitor live purchases.
app.js
const express = require("express");
module.exports = (config) => {
const app = express();
app.post("/newPurchase/:storeId", (req, res, next) => {
const { storeId } = req.params;
// trigger websocket message to `localhost:3000/livePurchases/:storeId`
// when client requests this route
});
return app;
};
But app.js is exported and run from another script, www.js. In real scenarios this is to connect a database before running the app.:
www.js
const app = require("../server/app")();
const port = process.env.PORT || "4000";
app.set("port", port);
app
.listen(port)
.on("listening", () =>
console.log("info", `HTTP server listening on port ${port}`)
);
module.exports = app;
So that means that the web socket server needs to be set up in www.js.
Below is a notifier service I got it from this tutorial, which seemed like it was trying to solve the problem I have, but it didn't explain how to implement it. It is a class that handles the websocket.
NotifierService.js
const url = require("url");
const { Server } = require("ws");
class NotifierService {
constructor() {
this.connections = new Map();
}
connect(server) {
this.server = new Server({ noServer: true });
this.interval = setInterval(this.checkAll.bind(this), 10000);
this.server.on("close", this.close.bind(this));
this.server.on("connection", this.add.bind(this));
server.on("upgrade", (request, socket, head) => {
console.log("ws upgrade");
const id = url.parse(request.url, true).query.storeId;
if (id) {
this.server.handleUpgrade(request, socket, head, (ws) =>
this.server.emit("connection", id, ws)
);
} else {
socket.destroy();
}
});
}
add(id, socket) {
console.log("ws add");
socket.isAlive = true;
socket.on("pong", () => (socket.isAlive = true));
socket.on("close", this.remove.bind(this, id));
this.connections.set(id, socket);
}
send(id, message) {
console.log("ws sending message");
const connection = this.connections.get(id);
connection.send(JSON.stringify(message));
}
broadcast(message) {
console.log("ws broadcast");
this.connections.forEach((connection) =>
connection.send(JSON.stringify(message))
);
}
isAlive(id) {
return !!this.connections.get(id);
}
checkAll() {
this.connections.forEach((connection) => {
if (!connection.isAlive) {
return connection.terminate();
}
connection.isAlive = false;
connection.ping("");
});
}
remove(id) {
this.connections.delete(id);
}
close() {
clearInterval(this.interval);
}
}
module.exports = NotifierService;
Where I left off implementing the `NotifierService`
I added the websocket server with the NotifierService in www.js
www.js with websockets added
const app = require("../server/app")();
const NotifierService = require("../server/NotifierService.js");
const notifier = new NotifierService();
const http = require("http");
const server = http.createServer(app);
notifier.connect(server);
const port = process.env.PORT || "4000";
app.set("port", port);
server
.listen(port)
.on("listening", () =>
console.log("info", `HTTP server listening on port ${port}`)
);
module.exports = app;
But now how do I send the websocket message from the /newPurchase route in app.js on the backend? If I create a new instance of NotifierService in app.js in order to use the notifierService.send method in the /newPurchase route, then the new NotifierService instance won't have access to the websocket connections because it would be a different instance than the one initiated on www.js.
Front End:
App.js
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import LiveStorePurchases from "./LiveStorePurchases";
function App(props) {
return (
<div className="App">
<Router>
<Switch>
<Route exact path="/livePurchases/:storeId">
<LiveStorePurchases />
</Route>
</Switch>
</Router>
</div>
);
}
export default App;
LivePurchaseServer.js
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
export default function LiveStorePurchases() {
let { storeId } = useParams();
const URL = "ws://127.0.0.1:4000?storeId=" + storeId;
const [ws, setWs] = useState(new WebSocket(URL));
useEffect(() => {
ws.onopen = (e) => {
newFunction(e);
function newFunction(e) {
alert("WebSocket Connected");
}
};
ws.onmessage = (e) => {
const message = e.data;
alert(message);
};
return () => {
ws.onclose = () => {
alert("WebSocket Disconnected");
setWs(new WebSocket(URL));
};
};
}, [ws.onmessage, ws.onopen, ws.onclose, ws, URL]);
return (
<div
style={{
color: "red",
fontSize: "4rem",
}}
>
store: {storeId}
</div>
);
}


/newPurchase/:storeIdroute being sent from Javascript in your web page? If not and it's a regular form post, then you can't send to a webSocket in that page anyway because the page will reload and it will have a new/different webSocket connection.localhost:4000/newPurchase/:storeIdroute would come from a page where a customer checks out to indicate that a purchase was made. I wouldn't be sending any websocket messages back to that checkout page. I would be sending the websocket message to a different page that the owner of the shop may be monitoring-localhost:3000/livePurchases/:storeId. So the message would be initiated in the code of the route/newPurchase/:storeIdbut it would be sent to the page/livePurchases/:storeId.io.to(someRoomName).emit(...).const notifier = new NotifierService();.... notifier.connect(server). How to I access it in app.js?