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
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:
server.js
builds the express app PLUS
the socket that needs the server as a dependency;server.js
imports ./routes/auth.js
, that calls server.js
again, so server.js -> auth.js -> server.js
, this is causing an infinite loop, we need to remove the link from auth.js
to the first step;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:
server.js
builds the express instance PLUS
a Node.js HTTP server, and calls the SocketIO Factory to build it;Socket.IO
Factory builds the instance, and sets a singleton variable with it;auth.js
now references the singleton
variable inside socket-factory.js
, rather than server.js
, so the new flow is:
server.js -> socket-factory.js
server.js -> auth.js
auth.js -> socket-factory.js
socket-factory.js
;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.