25

I only need socket.io to emit messages to clients, if a new object is inserted to database. So my idea was to emit the message directly from my controller’s insert-method. In my server.js file, i'm creating the socket.io object and try to make it accessible for other modules:

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

//make socket io accessible for other modules
module.exports.io = io;

In my controller I have tried using socket.io in this way:

var io = require('../server').io;
...
io.sockets.on("connection", function(socket){
  passportSocketIo.filterSocketsByUser(io, function (user) {
    return user.workingAt === socket.handshake.user.workingAt;
  }).forEach(function(s){
    s.send("news", insertedObject);
  });
});

And here I'm stuck. The "connection" event will never be fired and so the message will not be emitted. Is that the correct way to use socket.io in separate files? Unfortunately I cant find complex socket.io example.

Aryan
  • 3,338
  • 4
  • 18
  • 43
tschiela
  • 5,231
  • 4
  • 28
  • 35
  • can't you just call `io.sockets.emit()` ? – gherkins Oct 24 '13 at 07:11
  • i want to send the message not to all user. Therefore i need the socket object to filter the active sockets ... i updated my code – tschiela Oct 24 '13 at 07:15
  • 1
    I'd be tempted to either debug the app, and step through what happens as the sockets are created and filtered, or add some logging to accomplish the same effect. One of your assumptions about the way things are working will be wayward. I assume you want to send messages to specific sockets when an HTTP request happens? I think your approach should work, but personally I'd pull all the socket.io access in a single wrapper module, and export what I needed, e.g. `mysockets.start(port)` and `mysockets.sendNews(user,msg)` – Richard Marr Oct 24 '13 at 07:47
  • At the moment iam wrapping all the socket.io stuff in a seperate module. I report back – tschiela Oct 24 '13 at 07:54
  • I used a mix between **socket.io** and Node.js **Events**. Check [this anwser](https://stackoverflow.com/questions/34596689/using-socket-io-in-express-js-project/52103434#52103434). – J.C. Gras Aug 30 '18 at 18:46
  • Here is the your solution: https://stackoverflow.com/a/68167653/6482248 – Prathamesh More Jun 29 '21 at 05:07

4 Answers4

56

You are trying to invert the flow of control. The way to do it is for your controller to implement an interface (an API) that your server can use to pass control to.

A simple example would be:

In mycontroller.js

// no require needed here, at least, I don't think so

// Controller agrees to implement the function called "respond"
module.exports.respond = function(socket_io){
    // this function expects a socket_io connection as argument

    // now we can do whatever we want:
    socket_io.on('news',function(newsreel){

        // as is proper, protocol logic like
        // this belongs in a controller:

        socket.broadcast.emit(newsreel);
    });
}

Now in server.js:

var io = require('socket.io').listen(80);
var controller = require('./mycontroller');

io.sockets.on('connection', controller.respond );

This example is simple because the controller API looks exactly like a socket.io callback. But what if you want to pass other parameters to the controller? Like the io object itself or the variables representing end points? For that you'd need a little more work but it's not much. It's basically the same trick we often use to break out of or create closures: function generators:

In mycontroller.js

module.exports.respond = function(endpoint,socket){
    // this function now expects an endpoint as argument

    socket.on('news',function(newsreel){

        // as is proper, protocol logic like
        // this belongs in a controller:

        endpoint.emit(newsreel); // broadcast news to everyone subscribing
                                     // to our endpoint/namespace
    });
}

Now on the server we'd need a bit more work in order to pass the end point:

var io = require('socket.io').listen(80);
var controller = require('./mycontroller');

var chat = io
  .of('/chat')
  .on('connection', function (socket) {
      controller.respond(chat,socket);
  });

Notice that we pass socket straight through but we capture chat via a closure. With this you can have multiple endpoints each with their own controllers:

var io = require('socket.io').listen(80);
var news_controller = require('./controllers/news');
var chat_controller = require('./controllers/chat');

var news = io
  .of('/news')
  .on('connection', function (socket) {
      news_controller.respond(news,socket);
  });

var chat = io
  .of('/chat')
  .on('connection', function (socket) {
      chat_controller.respond(chat,socket);
  });

Actually, you can even use multiple controllers for each endpoint. Remember, the controllers don't do anything apart from subscribing to events. It's the server that's doing the listening:

var io = require('socket.io').listen(80);
var news_controller = require('./controllers/news');
var chat_controller = require('./controllers/chat');

var chat = io
  .of('/chat')
  .on('connection', function (socket) {
      news_controller.respond(chat,socket);
      chat_controller.respond(chat,socket);
  });

It even works with plain socket.io (no endpoints/namespaces):

var io = require('socket.io').listen(80);
var news_controller = require('./controllers/news');
var chat_controller = require('./controllers/chat');

io.sockets.on('connection', function (socket) {
    news_controller.respond(socket);
    chat_controller.respond(socket);
});
slebetman
  • 109,858
  • 19
  • 140
  • 171
  • Thanks for you awesome answer, that looks very streight forward. But i have one question: What if i want to use socket.io in more than one controller? – tschiela Oct 24 '13 at 08:14
  • 1
    That's covered in the second part - use separate endpoints for each controller. – slebetman Oct 24 '13 at 08:24
  • Added more examples to my answer. – slebetman Oct 24 '13 at 08:32
  • An additional hint: in my own project I load the controllers automatically at startup by listing the controllers directory and requiring all js files I find in there and registering their callbacks. It's express.js in my case but the idea is the same. – slebetman Oct 24 '13 at 08:35
  • I call the controller from my route like ... app.post('/api/test', TestCtrl.insert); and my controller looks like: module.exports.insert = function(req, res){ // is it possible to emit socket.io messages from here to inform all room clients about the new item? } – tschiela Oct 24 '13 at 12:50
  • 5
    You'd need a different controller to handle socket.io since it's a different service. But for emit only sure, you can just pass the socket.io in the same way. Have your controller accept `req,res,io` as arguments and then in app.post have the second argument be `function(req,res){TestCtrl.insert(req,res,io)}` – slebetman Oct 24 '13 at 14:13
  • Thanks for answer with examples :) – voila Dec 23 '13 at 05:02
  • That variable is the socket.io socket object representing the connection. You must be new to js since you think I'm passing the variable. I'm not. The socket.io library passes the socket object as an argument to a callback function. I'm merely declaring the function. In my declaration I happen to name the variable "socket". The variable name does not matter. See the socket.io documentation for further detail. – slebetman Apr 21 '14 at 14:08
6

You can do this very easily you have to only write socket connection in app.js and than you can use socket anywhere you want

In app.js file put code like below

 var http = require('http').createServer(app);
 const io = require('socket.io')(http);  

 io.sockets.on("connection", function (socket) {
 // Everytime a client logs in, display a connected message
 console.log("Server-Client Connected!");

 socket.join("_room" + socket.handshake.query.room_id);

 socket.on('connected', function (data) {

   });
});

const socketIoObject = io;
module.exports.ioObject = socketIoObject;

In any file or in controller you can import that object like below

 const socket = require('../app'); //import socket  from app.js

      //you can emit or on the events as shown 
 socket.ioObject.sockets.in("_room" + req.body.id).emit("msg", "How are You ?");
Aryan
  • 3,338
  • 4
  • 18
  • 43
0
//app.js or index.js
const app = express();
var http = require("http");
var server=http.createServer(app).listen(2525, (req, res) => {
  console.log("Server running on", 2525);
});
var socketIO = require("socket.io");
var io = socketIO(server);
global.io = io //Importent line this will define your io as global so you can use it anywhere.

//Add the below statement to your controller code
global.io.emit("eventname", "yourdata"); //Importent line
Dhaval Italiya
  • 386
  • 2
  • 7
-1
You can solve this problem declaring io instance as global variable.

at the last line of my app.js: IoInstance = require("./socket.io")(server);

at the './socket.io' :
const chatNamespace = require("./namespaces/chat");
const notificationsNamespace = require("./namespaces/notifications");
const auth = require("./middlewares/auth");

module.exports = (server) => {
  const io = require("socket.io").listen(server);

  io.of("/chats")
    .use(auth)
    .on("connect", (socket) => chatNamespace({ io, socket }));

  io.of("/notifications")
    .use(auth)
    .on("connect", (socket) => notificationsNamespace({ io, socket }));

  return io;
};

then, you can use the IoInstance wherever you want, event into your controller. Btw, I could have use it into my namespaces as well, but I've just realized it right now.

example of implementation in the testController.js:

module.exports = async (req, res) => {
  IoInstance.of("/notifications")
    .to("myUserId")
    .emit("sendNotification", ["test", "test1"]);
  return res.send("oioi");
};