Nate
Nate

Reputation: 4948

How can I emit events to connected sockets using socket.io from within my Express 4 routes?

This is a question other people have asked, but I can't manage to benefit from the answers they've been given, due to the different Express setup I have.

I've got socket.io implemented and working in a simple way on my server. This is how it works:

In bin/www:

#!/usr/bin/env node
var debug = require('debug')('gokibitz');
var app = require('../../server');

app.set('port', process.env.PORT || 3000);

var server = app.listen(app.get('port'), function() {
    debug('Express server listening on port ' + server.address().port);
});

var io = require('socket.io').listen(server);

io.on('connection', require('../routes/socket.js'));

All my routes and other Express setup is in ../../server.js.

var routes = require('./server/routes/index');
var user = require('./server/routes/user');
...

app.use('/', routes);
app.use('/api/user/', user);
...
app.use('*', routes);

Then in ../routes/socket.js, I've got this boilerplate:

module.exports = function (socket) {
    socket.emit('send:news', { hello: 'world' });

    setInterval(function () {
        socket.emit('send:time', {
            time: (new Date()).toString()
        });
    }, 1000);

    return socket;
};

This is working beautifully, I might add. But now I want to be able to emit events from the various routes in my quite-ordinary Express app, and I can't for the life of me figure out the right way to get a reference to the socket object I need.

Example: when a user makes a comment, I'd like to emit an event to all connected users notifying them of the new comment. From my routes file (./server/routes/user.js), how can I get access to the object I need to emit events?

Here's a skeleton of the relevant bits from a route file:

var express = require('express');
var router = express.Router();

router.post('/', function (req, res) {
  ...
});

module.exports = router;

The only place I can access it is in the ../routes/socket.js file, which is useless.

All of the routes are set in a app.js before there's any io or socket object to pass in.

Should I be able to require('socket.io') and use it somehow to emit to all connected sockets?

Is there a sensible way to store the connected sockets on ../routes/socket.js so it can be required and emitted to from other routes?

Can anyone steer me in the right direction?

Upvotes: 6

Views: 8467

Answers (4)

Vinit Singh
Vinit Singh

Reputation: 53

I have made a new file sockets.js in that file:

var socketio = require('socket.io');
var io;

module.exports = {
socketServer: function (app) {
    io = socketio.listen(app);

    io.on('connection', function (socket) {

        console.log(Object.keys(users));
        socket.on('message', function (data) {
            io.emit('updateChat', 'Hello World!')
        });
    });
  }
}

And in my app.js:

var express = require('express');
var app = express();
var http = require('http').createServer(app);
var port = process.env.PORT || 3000;

var io = require('./sockets').socketServer(http);
http.listen(port, function () {
    console.log('SERVER RUNNING.. PORT: ' + port)
});

This is working for me. Good Luck!

Upvotes: 0

Nate
Nate

Reputation: 4948

I was able to ultimately get things working using this example: https://github.com/expressjs/generator/issues/45#issuecomment-53719435

I created a new file called server/io.js:

var io = require('socket.io')();

io.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});

module.exports = io;

Then I updated server/bin/www to this:

#!/usr/bin/env node
var debug = require('debug')('gokibitz');
var app = require('../../server');
var io = require('../io');

app.set('port', process.env.PORT || 3000);

var server = app.listen(app.get('port'), function() {
    debug('Express server listening on port ' + server.address().port);
});

io.attach(server);

Then, in my route, I use this:

var io = require('../io');
...
io.emit(...);

The missing piece for me, at least, was the ability to create the io object separately, return the correct object, then use io.attach(server) inside bin/www to start it up at the right point.

Hopefully this will help someone else following in my footsteps.

Upvotes: 15

Exos
Exos

Reputation: 3988

I think you are confused with the concepts. The socket.io lib uses or emulate a websocket (bidirectional socket) with the client, and have not relation with routes.

You can send a notification to all sockets using the io object:

io.emit('message_to_all', true);

You have to an array on io.sockets, with all sockets.

You can uses namespaces or rooms to, I recomend you learn the documentation:

http://socket.io/docs/rooms-and-namespaces/#

Add something:

If you want to send a message to all people in the same route, you can join to a channel with the same name of the path.

For example, in the client:

var socket = io(window.location.href); // Or route..

And the server:

var nsp = io.of('/the/specific/route');
nsp.on('connection', function(socket){
  // ...
});
nsp.emit('message_to_all_in_route', data);

About the last question editing:

You can send the io object in request or response object to routes, using the Express midleware API:

For example:

#!/usr/bin/env node
var debug = require('debug')('gokibitz');
var app = require('../../server');

app.set('port', process.env.PORT || 3000);

var server = app.listen(app.get('port'), function() {
    debug('Express server listening on port ' + server.address().port);
});

var io = require('socket.io').listen(server);

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

io.on('connection', require('../routes/socket.js'));

And in the route:

route.post('/post/:id/comments', function (req, res) {

   // Do your logic

  var postio = res.io.of('/post/' + req.params.id);
  postio.emit('new_comment', commentData);

});

Upvotes: 3

jfriend00
jfriend00

Reputation: 707308

As I said in my comment, you can send to all connected clients with:

io.emit(msg, data);

This will require access to the io object that you created in your first module. The usual way to share that with your module with your routes modeule would be to export a method from the routes module that lets you pass it the io object and then after you've required in the routes module and created the io object, you can just call that method to pass the io object to the routes module and it can save it locally for future use.

io is just a shared object like the app object is. If you want a module to be able to use a shared object, the usual way is that you call some method in that module to share with it some objects that it can then use.

Upvotes: 1

Related Questions