37

I have socket.io working in app.js but when i am trying to call it from other modules its not creating io.connection not sure ?

app.js

var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var ditconsumer = require('./app/consumers/ditconsumer');
ditconsumer.start(io);
server.listen(3000, function () {
    console.log('Example app listening on port 3000!');
});

consumer.js

module.exports = {
    start: function (io) {
        consumer.on('message', function (message) {
            logger.log('info', message.value);
            io.on('connection', function (socket) {
                socket.on('message', function(message) {
                    socket.emit('ditConsumer',message.value);
                    console.log('from console',message.value);
                });
            });
        });
}
}
hussain
  • 6,587
  • 18
  • 79
  • 152

8 Answers8

54

Since app.js is usually kind of the main initialization module in your app, it will typically both initialize the web server and socket.io and will load other things that are needed by the app.

As such a typical way to share io with other modules is by passing them to the other modules in that module's constructor function. That would work like this:

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

// load consumer.js and pass it the socket.io object
require('./consumer.js')(io);

// other app.js code follows

Then, in consumer.js:

// define constructor function that gets `io` send to it
module.exports = function(io) {
    io.on('connection', function(socket) {
        socket.on('message', function(message) {
            logger.log('info',message.value);
            socket.emit('ditConsumer',message.value);
            console.log('from console',message.value);
        });
    });
};

Or, if you want to use a .start() method to initialize things, you can do the same thing with that (minor differences):

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

// load consumer.js and pass it the socket.io object
var consumer = require('./consumer.js');
consumer.start(io);

// other app.js code follows

And the start method in consumer.js

// consumer.js
// define start method that gets `io` send to it
module.exports = {
    start: function(io) {
        io.on('connection', function(socket) {
            socket.on('message', function(message) {
                logger.log('info',message.value);
                socket.emit('ditConsumer',message.value);
                console.log('from console',message.value);
            });
        });
    };
}

This is what is known as the "push" module of resource sharing. The module that is loading you pushes some shared info to you by passing it in the constructor.

There are also "pull" models where the module itself calls a method in some other module to retrieve the shared info (in this case the io object).

Often, either model can be made to work, but usually one or the other will feel more natural given how modules are being loaded and who has the desired information and how you intend for modules to be reused in other circumstances.

Rohit.007
  • 3,414
  • 2
  • 21
  • 33
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • so if i need `io` object do i need to create `io.on` connection in every module like you did for `consumer.js` – hussain Jul 21 '16 at 20:57
  • @hussain - If you are trying to listen for incoming messages from connected sockets, then you need `io.on('connection, ...)` to get to the connected sockets. There are certainly other ways to architect things where you have one central `io.on('connection, ...)` and other modules can contribute message listeners, but that's a bigger change in your code and we'd have to understand more about what you're doing to know the best thing to recommend. But, it also no big deal to have multiple `io.on('connection, ...)` listeners. `io` is an `eventEmitter` and is built to have lots of listeners. – jfriend00 Jul 21 '16 at 21:00
  • actually i updated my question code for consumer , i am already exporting Consumer into app.js , is it possible to add module.export function inisde Consumer object ? – hussain Jul 21 '16 at 21:07
  • Yeah.. I forgot about this approach. I actually like this suggestion better than mine. – Patrick Roberts Jul 21 '16 at 21:07
  • @hussain - Your modifications to consumer are perfectly compatible with this general scheme. If you want to keep your `start` exported method, then just pass `io` to the `start` method and get it from there. The point is that you have the `app` module pass the `io` object to your `consumer` module as part of the startup/initialization of the `consumer` module. But, what you show looks like you can just replace what you have with what I suggested and use a constructor function instead of a `start()` method - though either can work just fine. – jfriend00 Jul 21 '16 at 21:10
  • little confuse here do i still need `require('./consumer.js)(io);` can you edit your answer help me to understand better ? – hussain Jul 21 '16 at 21:18
  • @hussain - I added another example to my answer using the `.start()` method. The concept is identical whether it's the `.start()` method or a module constructor function. The idea is that you call some exported function in the consumer module and pass it the `io` instance variable so it can then use it. – jfriend00 Jul 21 '16 at 22:13
  • @jfriend00 I tried exactly way you are doing it , its printing `logger.log('info', message.value)` message but its not going inside `io.connection` function and i do not see any error. see my question – hussain Jul 22 '16 at 14:24
  • Thanks i got `io` working its sending string to client , now i really need to see how can i det message from consumer and pass it to `io` , Thanks again for the help! – hussain Jul 22 '16 at 15:25
  • how can I add this solution to my other module when I already have module exports with other functions? – gcfabri Mar 16 '17 at 16:19
  • @gcfabri - I can't really answer without seeing your code (which would go in your own question). But, whatever object you are exporting, you just add more methods to it. – jfriend00 Mar 16 '17 at 21:08
  • That solution doesn't work when you use the express-generator, since it create the "www" and "app.js" file and the app object is initialized before the io so you can't pass it to app.js – Benjamin Heinke Jan 30 '20 at 20:03
  • @BenjaminHeinke - Uh, yes. If you have a different startup or module layout, you have to adapt this concept to whatever you have. There is no `io` in the express-generator shell (that I'm aware of) so you can put it wherever you want. There's no reason that you can't change where things are initialized to suit your needs too. – jfriend00 Jan 30 '20 at 21:33
  • @jfriend00 I found the solution here: https://stackoverflow.com/questions/24609991/using-socket-io-in-express-4-and-express-generators-bin-www – Benjamin Heinke Jan 30 '20 at 22:00
  • how can I emit data to the socket outside the start function to that sockets – Fayaz Mar 25 '20 at 18:58
  • @Fayaz - That would be specific to the layout of your code/modules. I'd suggest you post a new question and show your specific code layout. It would also depend upon what context you're in when you want to send data and how you determine which socket you want to send the data to. – jfriend00 Mar 25 '20 at 19:06
  • @jfriend00, thank you. I have posted a new question here, please try to answer there. https://stackoverflow.com/questions/60855911/how-can-i-export-a-function-from-the-entry-pointindex-js-and-use-it-in-other-j – Fayaz Mar 25 '20 at 19:18
36

If you want to avoid the global scope, make your io exist in a separate file like this:

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

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

exports.initialize = function(server) {
  return io = sio(server);
};

Then in app.js:

var server = require('http').createServer(app);
var io = require('./io').initialize(server);
require('./app/consumers/ditconsumer'); // loading module will cause desired side-effect
server.listen(...);

and in consumer.js:

require('../io').io().on('connection', function(socket) {
  logger.log('info', message.value);
  socket.on('message', function(message) {
    socket.emit('ditConsumer',message.value);
    console.log('from console',message.value);
  });
});
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
5

I found a simple solution. Use a global variable in app.js and access it from the other files.

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

spydon
  • 9,372
  • 6
  • 33
  • 63
Aung Ko Man
  • 502
  • 6
  • 13
2

You can make a singleton instance in just 4 lines.

In websocket.js write your server configuration code.

const socketIO = require('socket.io');
const server = require('http').createServer();
server.listen(8000);

module.exports = socketIO(server);

Then in your consumer.js just require the file

const socket = require('./websocket');

/* API logic here */

socket.emit('userRegistered', `${user.name} has registered.`);
Dušan Minić
  • 41
  • 1
  • 4
2

What worked best for me was to use a callback function which exports the socket.io instance. app.js:

var server = require('http').createServer(app);
var io = require('socket.io')(server);
io.on('connection', function(socket) {
    socket.on('message', function(message) {
        logger.log('info',message.value);
        socket.emit('ditConsumer',message.value);
        console.log('from console',message.value);
    });
});
function getSocketIo(){
    return io;
}
module.exports.getSocketIo=getSocketIo

and in the consumer.js

const app=require('./app.js')
const io=app.getSocketIo()
1

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;
http.listen(port, () => {
    console.log('Magic happens on port ' + port); // shoutout to the user
});

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 ?");

This solved my problem easily

Aryan
  • 3,338
  • 4
  • 18
  • 43
1

You can create a singleton class for socket-io.

It looks so clean and modular.

Here is my folder structure.

node.js folder structure

Socket.io.ts

In this file, I initializing socket-io, and created publishEvent() method so I can publish events.

import {
  Server,
  Socket
} from "socket.io";
import {
  Events
} from "./utils";

export class SocketInit {
  private static _instance: SocketInit;

  socketIo: Server;

  constructor(io: Server) {
    this.socketIo = io;
    this.socketIo.on("connection", (socket: Socket) => {
      console.log("User connected");
    });
    SocketInit._instance = this;
  }

  public static getInstance(): SocketInit {
    return SocketInit._instance;
  }

  public publishEvent(event: Events, data: any) {
    this.socketIo.emit(event, data);
  }
}

Server.ts

import "reflect-metadata";
import {
  config
} from "dotenv";
config();
import http from "http";
import express, {
  Request,
  Response
} from "express";
import {
  Server,
  Socket
} from "socket.io";
import mongoose from "mongoose";
import cors from "cors";
import path from "path";
import morgan from "morgan";
import {
  SocketInit
} from "./socket.io";
import {
  downloadsRouter
} from "./routes/downloads";

const app = express();

const server = http.createServer(app);

export const io = new Server(server, {
  cors: {
    origin: "*"
  },
});

//Initilize socket
new SocketInit(io);

mongoose
  .connect("mongodb://localhost:27017/youtube", {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() => {
    console.log("Connected to database");
  })
  .catch((error) => {
    throw error;
  });

app.use(morgan("dev"));
app.use(express.json());
app.use(express.urlencoded({
  extended: true
}));
app.set("view engine", "ejs");
app.use(express.static(path.join(__dirname, "views")));
app.use(cors());
app.use(downloadsRouter);

app.get("/", (req: Request, res: Response) => {
  res.render("index");
});

server.listen(3000, () => {
  console.log("Server running up 3000");
});

download-queue.ts In this file where I performing some download tasks and emit events for clients.

import Bull from "bull";
import ytdl from "ytdl-core";
import fs from "fs";
import {
  Video
} from "../models/video";
import {
  Events
} from "../utils";
import {
  SocketInit
} from "../socket.io";

const downloadQueue = new Bull("download queue", {
redis: {
  host: process.env.REDIS_HOST!,
  port: parseInt(process.env.REDIS_PORT!),
},
});
);

downloadQueue.process((job, done) => {
  return new Promise((resolve, reject) => {
    const title = Math.random().toString();
    const {
      youtubeUrl
    } = job.data;

    //Get singleton instance
    const socket = SocketInit.getInstance();

    ytdl(youtubeUrl)
      .pipe(fs.createWriteStream(`${process.cwd()}/downloads/${title}.mp4`))
      .on("finish", async() => {
        socket.publishEvent(Events.VIDEO_DOWNLOADED, title);

        console.log("Download complete");

        const file = `${process.cwd()}/downloads/${title}.mp4`;

        const video = new Video({
          title,
          file,
        });

        await video.save();

        done();

        resolve({
          title
        });
      })
      .on("ready", () => {
        console.log("Download started");
        socket.publishEvent(Events.VIDEO_STARTED, title);
      })
      .on("error", (error) => {
        socket.publishEvent(Events.VIDEO_ERROR, error);
        done(error);
        reject(error);
      });
  });
});

export {
  downloadQueue
};
Prathamesh More
  • 1,470
  • 2
  • 18
  • 32
0

I created a file socket.service.ts with a class SocketService and in app.tsI called the constructor with the http. This will also work with pure javascript, just change the imports and exports ...

import * as socketIo from 'socket.io';

export class SocketService {

  io: any;

  constructor(http) {
    this.io = socketIo(http)
    this.io.set('origins', '*:*');
    this.io.on('connection', function (socket) {
      console.log('an user connected');
      socket.on('disconnect', function () {
        console.log('user disconnected');
      });
    });

    http.listen(3001, function () {
      console.log('socket listening on *:3001');
    });

  }

}

in app.ts call is like:

import * as express from 'express';

import { SocketService } from './services/socket.service';

const app = express(); 
var http = require('http').Server(app);

// ...

new SocketService(http);

// ...

module.exports = app;

Please notice that everytime you call the constructor a new instance will be created. To avoid this use the singleton pattern :)

kenny
  • 1,628
  • 20
  • 14