39

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); }); }); }); } } 
0

8 Answers 8

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.

Sign up to request clarification or add additional context in comments.

17 Comments

so if i need io object do i need to create io.on connection in every module like you did for consumer.js
@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.
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 ?
Yeah.. I forgot about this approach. I actually like this suggestion better than mine.
@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.
|
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); }); }); 

8 Comments

i added var socket = require('../index').io() into consumer.js but its throwing error TypeError: require(...).io is not a function
Is the new file you added called index.js?
Does index.js have the code exports.io = function () { return io; };?
Are you sure the file is located in the correct location relative to consumer.js?
Yes i mean index.js module is being called but not the method io
|
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);

1 Comment

This is great. But does anyone know of any security issues with this?
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.`); 

Comments

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() 

Comments

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

Comments

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 };

Comments

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 :)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.