node.jssocket.iocircular-dependency

Circular Depedency Socket.io NodeJS error


I'm building a chatting app and I'm at the point where I'm trying to make use of socket.io to handle real time messages. But the way I have structured my code in the backend I have a server.js and a controller/authController.js that handles all API functions called in the routes to the frontend so like I keep seeing everywhere, people make use of the socket.io in the server, but I want to use it in the authController.js along with other functions like the login, register, sendMessage, ..... but I have come accross the error message below:

(node:1904) Warning: Accessing non-existent property 'io' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)

When I run the command asked I get this:

$ node --trace-warnings ...
node:internal/modules/cjs/loader:1078
  throw err;
  ^

Error: Cannot find module 'C:\Users\flex zone\Desktop\Mean app v1\backend\...'
←[90m    at Module._resolveFilename (node:internal/modules/cjs/loader:1075:15)←[39m
←[90m    at Module._load (node:internal/modules/cjs/loader:920:27)←[39m
←[90m    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)←[39m
←[90m    at node:internal/main/run_main_module:23:47←[39m {
  code: ←[32m'MODULE_NOT_FOUND'←[39m,
  requireStack: []
}

Node.js v18.16.0

Here's the server.js I did:

require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
require('./config/db').connectDB()
const authRoutes = require('./routes/auth');
const { Server } = require('socket.io')

const PORT = process.env.PORT || 3000;

const app = express();
app.use(bodyParser.json());
app.use(cors());

app.use('/api', authRoutes)

const server = app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});

const io = new Server(server)

module.exports = {
    io,
}

Here's the authController.js, the part I imported the io:

const { io } = require('../socket.io.js');

This 2 codes seem to depend on one another as I researched, but how can I resolve this please


Solution

  • As you mentioned, the code has a circular dependency, and it can be fixed, also, the code tries to instantiate the SocketIO server with an express app instance, however, Server expects a Node.js HTTP server instance.

    The current flow is:

    To solve that, we can use the Singleton Design Pattern because it ensures that a class/variable has only one instance, while providing a global access point to this instance. Let's segregate the socket instantiation to a separate factory file, what we need to achieve is the avoidance of a circular reference, so let's think about the new solution as:

    Note: The code below uses ES6 Modules as they're more modern:

    socket-factory.js:

    import { Server } from 'socket.io';
    
    /**
     * @type {Server}
     */
    export let socketIOSingleton = null;
    
    export function socketIOFactory(serverInstance) {
        socketIOSingleton = new Server(serverInstance)
    }
    

    server.js:

    import authRoute from './routes/auth.js';
    
    import express from 'express';
    import http from 'http';
    import { socketIOFactory, socketIOSingleton } from './socket.js';
    
    const PORT = process.env.PORT || 8000;
    
    const app = express();
    const server = http.createServer(app);
    
    app.get('/api/auth', authRoute)
    
    socketIOFactory(server)
    
    socketIOSingleton.on("connection", (socket) => {
        console.warn("socket connected")
    });
    
    server.listen(PORT, () => {
        console.log(`Server running on port ${PORT}`);
    });
    

    auth.js:

    import { socketIOSingleton } from "../socket.js";
    
    export default async function handler(req, res) {
        // broadcast a message to all connected clients
        socketIOSingleton.emit("message", "Hello everyone!");
        return res.status(200).json({ message: 'Hello there!' });
    }
    

    Also, if you find a hard time trying to find where a circular dependency is, please check Madge, as it's a really good package to help you find them.