Zachary Markham Zachary Markham - 25 days ago 10
Node.js Question

Why isn't socket.io callback being triggered inside an express route

I've got the following setup (important bits only for brevity):

app.js

...
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);

server.listen(port, function() {
console.log(`Server is listening on port: ${port}`);
});

io.on('connection', function (socket) {
console.log('connection');
});

const routes = require('./routes/index')(io, passport);
app.use('/', routes);


index.js (server)

router.get('/game/:id', isAuthenticated, (req, res) => {

if (req.id)
{
var game = Game.findOne({_id: req.id}, (err, obj) => {
io.on('getGameInfo', (socket) => {
io.emit('gameInfo', obj);
});

res.render('game', obj);
});
}
else
{
// Id not valid, do something
}
});


client:

const socket = io('http://localhost:3000');

socket.on('gameInfo', function(data) {
console.log(data);
}.bind(this));

socket.on('connect', () => {
socket.emit('getGameInfo');
});


So basically I want to emit a getGameInfo call once I know the client has connected, and the getGameInfo listener has been set up in the game route. But when I emit the getGameInfo from the client, the server callback isn't being hit. I'm not sure if I'm missing something obvious, or if this is a closure issue, or if I'm just having one of those days, or if I'm going about this entirely the wrong way.

Answer

There are multiple problems here. I'll start by showing the correct way to listen for an incoming socket.io message on the server:

io.on('connection', function (socket) {
    // here's where you have a new socket and you can listen for messages
    // on that socket
    console.log('connection');
    socket.on('gameInfo', (data) => {
        socket.emit('gameInfo', obj);
    });
});

Some of the issues:

  1. On the server, you listen for messages via the socket object, not via the io object. So, you would typically add these event listeners in the io.on('connection', ...) handler because that's where you first see newly connected sockets.

  2. You pretty much never want to add event listeners inside an Express route handler because that is called many times. In addition, at the moment the route handler is called, the browser has not yet received the page and will not yet be connected so even if this was an OK place to do stuff, the page is not yet connected anyway.

  3. When you want to send a message back to just one connection, you send it with socket.emit(), not io.emit(). io.emit() broadcasts to all connected clients which I don't think is what you want.

  4. I'd suggest you not overload the same message name for client and server to mean two different things as this can lead to confusion when reading code or if you ever share some code between client and server. You client is really sending a "getGameInfo" message and then your server responds with a "gameInfo" message that contains the gameInfo.

If, in a route handler, you want to .emit() to the socket from that page which it looks like you are trying to do, then you have to do some work to create a link between the session of the current page and the socket for that page. There are a number of ways to do that. If you're using any session middleware, you can record the socket in the session at the point the socket connects. Then, from your express routes, you can get that socket from the session object at any time.