39

I have express/nodejs api. I am adding socket.io feature to it. Currently all of my routes are in separate folders and I include them in server.js file and use them as app.use() function.

In server.js file, I also start the express server by listening to a particular port such as 3000 as below.

let server = app.listen(3000);

According to all google searches what I found is that I need to pass server variable to initialize socket.io like following.

let io = require('socket.io')(server);

Now the question is that since it needs this variable then how can I use socket.io in my routes files which are in different folder to emit and receive events from client?

UPDATE

in server.js file

let route = require('./routes/route');

let app = express();

let server = app.listen(3000);

console.log('Listening to port');

let io = require('socket.io')(server);

app.use('/api/1.0/events', route(io));

in route.js file

let express = require('express');

module.exports = (io) => {
    console.log('IO: ', io);
};

UPDATE 2

server.js file

let express = require('express');
let events = require('./routes/events');
let app = express();
let server = app.listen(3000);

let io = require('socket.io')(server);


app.use(function(request, response, next) {
    request.io = io;
    next();
});

app.use('/events', events);

events.js file

let express = require('express');

let Events = require('../models/events');

apiRoutes.post('/new', function(request, response) {
    let newEvent = new Events(request.body);

    newEvent.save((error, result) => {
        if (error) {
            response.json(error);
        } else {
            // console.log('ELSE');
            // request.io.on('connect', socket => {
                // console.log('LISTENING TO SOCKET...');

                request.io.on('EventCreated', data => {
                    console.log('DATA ON Server: ', data);
                });
            // });

            response.json({
                success: true,
                message: 'New event created'
            });
        }
    });
});
null
  • 1,112
  • 3
  • 12
  • 28
  • export your `io` variable to other files using module.exports – wrangler Dec 15 '17 at 18:20
  • Thanks for your reply but how can I export? I mean in which file. I am requiring it in server.js file. Can you provide any sample code? – null Dec 15 '17 at 18:26
  • 1
    You never want to install an event handler inside a request handler like `request.io.on()` because you will add a new event handler EVERY time that route is hit and they will pile up forever. Plus, you don't listen for individual socket events with `io.on()`. You listen for message events with `socket.on()`. So, your whole approach here is wrong. You'd have to back up and describe from the beginning the overall problem you're trying to solve for us to help you with that. But, since it's been days on a different part of the question, I'm thinking you need to start a new question for that. – jfriend00 Dec 17 '17 at 20:05
  • The problem that I am trying to solve is that user will create an event. User will fill the form and press create an event button(the form gets closed upon clicking on Create an Event button and user sees the list of events page). It sends data to the server and store db. Once it is stored then emit new event and listen to that event on client. And update the list of events on events page with newly created event. – null Dec 17 '17 at 20:20
  • can you look into this one https://stackoverflow.com/questions/47877343/socketio-with-expressjs-routes? – null Dec 19 '17 at 03:10

4 Answers4

61

There are multiple ways to share the io variable with route files.

  1. When you require() in your route file, pass it the io variable as a constructor argument.

  2. Use app.set("io", io) so you can then use let io = app.get("io") in any file that has access to the app object.

  3. Create a middleware that puts the io object on every req object so you can access it from there any time.


Here's an example of passing it as a constructor argument to the router file:

let server = app.listen(3000);
let io = require('socket.io')(server);

// load other routers
app.use(require("./someRouterFile.js")(io));

// in someRouterFile.js
const express = require('express');

module.exports = function(io) {
    let router = express.Router()

    // define routes
    // io is available in this scope
    router.get(...)

    return router;
}

Here's an example of the app.set() scheme:

let server = app.listen(3000);
let io = require('socket.io')(server);
app.set("io", io);

Then, anywhere in your routes that you have access to the app object, you can get it with:

let io = app.get("io");

Here's an example of using a middleware to set the io object onto every req object so it's available from all routes.

let server = app.listen(3000);
let io = require('socket.io')(server);

// place this middleware before any other route definitions
// makes io available as req.io in all request handlers
app.use(function(req, res, next) {
    req.io = io;
    next();
});

// then in any express route handler, you can use req.io.emit(...)

Here's an example of using an argument to the module constructor without middleware:

// in mysocket.js
module.exports = (io) => {
    console.log('IO: ', io);
    io.on('connect', socket => {
       // handle various socket connections here
    });

    // put any other code that wants to use the io variable
    // in here


};

Then, in your main file:

let server = app.listen(3000);
let io = require('socket.io')(server);

// initialize my socketio module and pass it the io instance
require('./mysocket.js')(io);
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thanks, I already tried app.set() and middleware approaches somehow they didnt work for me. The first one sound interesting but somehow I am getting this error `/node_modules/express/lib/router/index.js:458 throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn)) ^ TypeError: Router.use() requires a middleware function but got a undefined ` – null Dec 15 '17 at 21:47
  • @null - I'd have to see your actual code you tried to know how to help you fix it. – jfriend00 Dec 15 '17 at 22:11
  • @null - Well your attempt at middleware is just a wrong implementation. Please look at the middleware or router implementations in my answer and copy from one of those. You are calling `route(io)` and passing the return value from that to the middleware, do for that to work it would have to return another function that expects middleware parameters, but your `route(io)` function returns nothing, thus you get the error you get. – jfriend00 Dec 16 '17 at 00:00
  • It is not a middleware actually as you can see. I am trying to implement your first approach. – null Dec 16 '17 at 01:17
  • @null - Then, just call the constructor with `route(io)`, not `app.use(route(io))`. When you use `app.use()` you have to follow the rules for middleware. – jfriend00 Dec 16 '17 at 03:04
  • I am using you first approach i.e. passing into constructor. With the first approach where can I use `io.on('connect')` and how using this approach? – null Dec 16 '17 at 18:33
  • @null - See the last example I added to my answer. – jfriend00 Dec 16 '17 at 18:38
  • Even with first approach i.e. `app.use()` does not work anymore as app.use() need a proper middleware function to be passed at parameter. So `require('./something.js')(io)` does not work. – null Dec 16 '17 at 18:44
  • I already tried that last example before posting this question which somehow didn't work. – null Dec 16 '17 at 18:46
  • @null - That last example was JUST added to my answer. If you implement it properly, it will work. If you think you are implementing this and it isn't working, then I need to see your exact code to see what you're doing differently. – jfriend00 Dec 16 '17 at 18:48
  • ok. Lets try that middleware as the other seem not to be working. where can I have the `io.on('connect')` using middleware approach? – null Dec 16 '17 at 19:49
  • @null - I don't get why you're asking me all these questions or having all these troubles. You must have something else more fundamentally wrong in your code. You can use `io.on('connect', ...)` anywhere that has the `io` variable in scope just like all my examples show. – jfriend00 Dec 16 '17 at 19:52
  • :-) the reason I am asking is that I have to use emit and on functions on io inside `io.on('connect')` callback. Isn't it? So even if I pass the io anywhere in routes and they have access to io I still need to use `connect` to make work. – null Dec 16 '17 at 20:16
  • @null - It depends upon what you're trying to do. You don't need to use `io.on('connec', ...)` in order to just broadcast `io.emit()` to all connected sockets or to broadcast to all in a room. But, if you want to handle an individual incoming message from a particular socket, you would use `io.on('connect', ...)`. So, can't really help you any further conceptually. Have to deal in real and actual code. – jfriend00 Dec 16 '17 at 20:24
  • can you check my update 2 in my question. I think doing everything right. – null Dec 17 '17 at 19:43
  • @jfriend00 - GREAT answer! About option - 3 (middleware). So in a route when socket is disconnected and need to be connect again (cause you change location - path) you do it by - io.on('connect', function(socket){}) (shouldn't it be 'connection' - on server side ??) and it will NOT add a new event handler EVERY time that route is hit? You can for example set a specific room and send by req.io.in(room).emit({}) some emits to specific users without stacking the event handler? Cause when you have settings for socket.io ('connection', 'disconnect', etc.) in APP.JS file and you duplicate – b4rtekb Jul 18 '20 at 19:19
  • 1
    @b4rtekb - On the server-side, the `connect` and `connection` events are [synonyms](https://socket.io/docs/server-api/#Event-%E2%80%98connection%E2%80%99) (same thing). – jfriend00 Jul 18 '20 at 19:22
  • the 'connection' handler in your route js file, my tests shows that socket.io handler is duplicated - every time the user hit the route he is getting x2 the emits. Honestly I have no idea how to resolve that. – b4rtekb Jul 18 '20 at 19:25
  • @b4rtekb - I'm not sure what file you're asking about. In this line of code: `app.use(require("./someRouterFile.js")(io));`, it only calls `require("./someRouterFile.js")(io)` once. That returns the middleware function that is called repeatedly. So, you have to install the listeners in the part that is called once, not inside the middleware function that gets returned. Since I can't see what your code looks like, I can't really diagnose it. If you can't figure it out, I would suggest you file your own question and show your actual code. You can leave me a link here to the new question. – jfriend00 Jul 18 '20 at 19:28
  • @b4rtekb - If you're talking about the file in my answer labelled mysocket.js, that is not middleware and should only be called once. – jfriend00 Jul 18 '20 at 19:30
  • You are right - here is the question with code - https://stackoverflow.com/questions/62973624 – b4rtekb Jul 18 '20 at 20:47
6

Sharing my Solution that I've used

Another solution for this problem (that I actually used) is that we can make our io (socket.io object) object global.

Simply assign io to the global.io.

The io object is in index.js, after creating and setting up the io object simply do global (i-e global.io = io;), now this io object is accessible in any route that we are working within our project.

Rizwan Amjad
  • 329
  • 4
  • 11
  • i am refactoring project from using single `main.js` file to separated route and middleware files. previously i had `var logged_in_users = {};` and `var users_in_rooms = {};` in `main.js` to keep track of socket id's etc and middleware defined in the same file could access and update those variables. now i have moved to separate middleware and route files, it seems middleware files don't have access to those global variables? i get errors: `ReferenceError: logged_in_users is not defined` from the middleware files. do u have any suggestions, or know of any resources, for how to fix this? – user1063287 Dec 01 '21 at 05:04
5

In server.js:

module.exports.getIO = function(){
     return io;
}

In your route file:

var theServer = require('<path to server.js>');
var iovar = theServer.getIO(); //your io var
wrangler
  • 3,454
  • 1
  • 19
  • 28
0

The middleware solution that worked for me —

index.js:

import express from 'express'
import mongoose from 'mongoose'
import cors from 'cors'
import http from 'http'
import { Server } from 'socket.io'
import staticRouter from './staticRouter.js'
import liveRouter from './liveRouter.js'

// Necessary for using 'require' for socket.io
import { createRequire } from 'module'
const require = createRequire(import.meta.url)


const startServer = async () => {
  
  const app = express()

  app.use(express.json())

  app.use('/static', staticRouter)

  app.use(cors())

  const server = http.createServer(app)

  const io = new Server(server, {
    cors: {
      origin: "origin-url",
      methods: ["GET", "POST", "PUT", "DELETE"],
    },
  })

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

  app.use('/live', liveRouter)

  await mongoose.connect('your-mongo-endpoint') //Connecting to mongoose

  server.listen(3000)
}

startServer()

liveRouter.js:

import express from 'express'

import { liveConnection } from './live.js'

// Routes
const liveRouter = express.Router()

// Live Updates
liveRouter.route('/live-connection')
  .put(liveConnection)


export default liveRouter

and live.js:

export const liveEventConnection = async (req, res) => {

  console.log('req.io ->', req.io)

  // In any express route handler, you can use req.io.emit(...)
  
}
Philip Sopher
  • 627
  • 2
  • 8
  • 19