So I am working on a MEAN stack project where in node I will emit the data to UI and in frontend I had some widgets ( as charts like gauge , line , pie and table ) where user can add dynamically whatever widget they need , now in backend I use reddis , socket io and nodejs , currently my code works fine but I want to optimize it as what if multiple devices connected and alot of payloads received , this function will hit through postman api for now ,

What I tried was i store the dashboard IDs that are connected in socket room and in save action I sums the data values of existing fields and make it as one , and used reddis to cache some data and get that if not then fetch from db and removed some .map() and used normal for loops

And I use worker file for inserting the reddis saved data in monogodb

My main data emit data nodejs file :


const { dbOperation } = require("../helper/db_operation");
const catchAsync = require("../util/cath_async");
const { getDeviceCollection } = require("../util/db");
const { ObjectId } = require("mongodb");
const { insertDataToRedis, InsertDataForTriggersToRedis } = require("../redis/RedisData.js");
const { client } = require("../redis/redisConnect.js");

exports.iotDeviceSaveAction = catchAsync(async (req, userdata, db, next, result) => {
const data = req.body.data;
const fk_device_id = req.body.fk_device_id;

if (!fk_device_id) {  
    return result("Device ID Is Required", 400);  
}  
if (!data) {  
    return result("Invalid Data", 400);  
}  
try {  
    if (Array.isArray(data) && data?.length == 0) return result("Data is empty", 200);  

    const deviceKey = `device:${fk_device_id}`;  
    const isCollectionExists_in_redis = await client.get(deviceKey);  
    let deviceCollection = isCollectionExists_in_redis;  

    if (!deviceCollection) {  
        const deviceModel = await db.collection("manage-device").findOne(  
            { _id: new ObjectId(fk_device_id) },  
            {  
                projection: { fk_collection_name: 1 },  
            }  
        );  

        if (!deviceModel) {  
            return result("Device not found", 400);  
        }  

        if (!deviceModel.fk_collection_name) {  
            return result("Device collection not found", 400);  
        }  

        deviceCollection = deviceModel.fk_collection_name;  
        await client.set(deviceKey, deviceCollection);  
    }  

    const now = new Date();  
    const hashmap = data.reduce((acc, item) => {  
        for (const [key, value] of Object.entries(item)) {  
            if (typeof value === "number" && !isNaN(value)) {  
                acc[key] = (acc[key] || 0) + value;  
            } else {  
                acc[key] = value;  
            }  
        }  
        return acc;  
    }, {});  

    hashmap.fk_device_id = fk_device_id;  
    hashmap.timestamp = now;  

    const formattedData = [hashmap];  

    const data_for_batch_save = {  
        insert_data: formattedData,  
        db_name: userdata.db_name,  
    };  

    const data_for_triggers = {  
        insert_data: [hashmap],  
        db_name: userdata.db_name,  
    };  
    InsertDataForTriggersToRedis(data_for_triggers);  
    insertDataToRedis(data_for_batch_save);  

    const socketIDS = Array.from(global.socket.sockets.sockets.values());  

    const socketsWithDB = socketIDS.filter((socket) => socket.dbID);  

    if (socketsWithDB.length <= 0) return result("Dashboard not found", 400);  

    const widgetsWithKeys = [];  

    for (let i = 0; i < socketsWithDB.length; i++) {  
        const DashboardID = socketsWithDB[i].dbID;  

        const dashboardKey = `dashboard:${DashboardID}`;  

        let dashboardCollection = await client.get(dashboardKey);  

        if (!dashboardCollection) {  
            const dashboardModel = await db.collection("manage-dashboard").findOne({ _id: new ObjectId(DashboardID) }, { projection: { widgets: 1 } });  
            if (!dashboardModel) {  
                continue;  
            }  
            if (!Array.isArray(dashboardModel.widgets)) {  
                dashboardModel.widgets = [];  
            }  
            await client.set(dashboardKey, JSON.stringify(dashboardModel.widgets));  
        } else {  
            dashboardCollection = JSON.parse(dashboardCollection);  
        }  

        const getWidgetsBasedOnDeviceID = Array.isArray(dashboardCollection) ? dashboardCollection.filter((widget) => widget.deviceID === deviceCollection) : [];  

        if (getWidgetsBasedOnDeviceID.length <= 0) return result("Widgets not found", 400);  

        for (let k = 0; k < getWidgetsBasedOnDeviceID.length; k++) {  
            const widget = getWidgetsBasedOnDeviceID[k];  
            const widgetId = widget._id.toString();  
            const values = [];  

            if (widget.type === "bar-chart") {  
                if (!widget.widgetOptions || !widget.widgetOptions.labelValuePairs || widget.widgetOptions.labelValuePairs.length <= 0 || widget.widgetOptions.labelValuePairs[0].label === widget.widgetOptions.xAxis) {  
                    continue;  
                }  
            }  

            if (widget.type === "stack-chart") {  
                if (!widget.widgetOptions || !widget.widgetOptions.labelValuePairs || widget.widgetOptions.labelValuePairs.length <= 0) {  
                    continue;  
                }  
            }  

            for (let i = 0; i < formattedData.length; i++) {  
                const dataItem = formattedData[i];  
                if (!dataItem) continue;  

                if (widget.type === "table") {  
                    values.push(dataItem);  
                } else if (widget.type === "gauge-chart") {  
                    const valueField = widget.widgetOptions?.valueField;  
                    if (valueField && dataItem[valueField] !== undefined) {  
                        values.push({  
                            value: dataItem[valueField],  
                        });  
                    }  
                } else if (widget.type === "pie-chart" || widget.type === "line-chart") {  
                    if (widget.widgetOptions?.labelValuePairs) {  
                        for (let a = 0; a < widget.widgetOptions.labelValuePairs.length; a++) {  
                            const pair = widget.widgetOptions.labelValuePairs[a];  
                            if (dataItem[pair.value] !== undefined) {  
                                values.push({  
                                    key: dataItem[pair.label] ?? pair.label,  
                                    value: dataItem[pair.value],  
                                });  
                            }  
                        }  
                    }  
                } else if (widget.type === "bar-chart") {  
                    for (let b = 0; b < widget.widgetOptions.labelValuePairs.length; b++) {  
                        const pair = widget.widgetOptions.labelValuePairs[b];  
                        if (dataItem[pair.value] !== undefined) {  
                            values.push({  
                                value: dataItem[pair.value],  
                                key: String(pair.value),  
                                xAxis: dataItem[widget.widgetOptions.xAxis],  
                            });  
                        }  
                    }  
                } else if (widget.type === "stack-chart") {  
                    for (let c = 0; c < widget.widgetOptions.labelValuePairs.length; c++) {  
                        const pair = widget.widgetOptions.labelValuePairs[c];  
                        if (dataItem[pair.value] !== undefined) {  
                            values.push({  
                                value: dataItem[pair.value],  
                                key: String(pair.label),  
                                xAxis: dataItem[widget.widgetOptions.xAxis],  
                            });  
                        }  
                    }  
                }  
            }  

            widgetsWithKeys.push({  
                widgetId: widgetId,  
                values: values,  
            });  
        }  
    }  

    if (widgetsWithKeys.length > 0 && socketsWithDB.length > 0) {  
        global.socket.emit("newData", {  
            Widgetdata: widgetsWithKeys,  
        });  
    }  
    return result("Success", 200);  
} catch (error) {  
    console.log("error", error);  
    return result(error.message || "Error", 400);  
}

});

My socket handler :

const socketIO = require("socket.io");

const { createAdapter } = require("@socket.io/redis-adapter");

const { connectRedis } = require("../redis/redisConnect.js");

async function setUpSocket(server) {

    const socket = socketIO(server, {

        cors: {

            origin: "\*",

        },

    });

    const pubClient = await connectRedis();

    const subClient = await pubClient.duplicate().connect();

    socket.adapter(createAdapter(pubClient, subClient));

    global.socket = socket;

    socket.on("connection", (socket) =\> {

        console.log("Socket connected", socket.id);

        socket.on("disconnect", () =\> {

            console.log("Socket disconnected", socket.id);

        });

        socket.on("dashboard-socket", (dbID) =\> {

            if (!dbID) return;

            if (socket.dbID) {

                socket.leave(socket.dbID);

                console.log(\`Socket ${socket.id} left room: ${socket.dbID}\`);

            }

            socket.join(dbID);

            socket.dbID = dbID;

            console.log(\`Socket ${socket.id} joined room: ${dbID}\`);

        });

        socket.on("error", (err) =\> {

            console.error("Unhandled socket error:", err);

        });

    });

}

module.exports = setUpSocket;

Any advice or help would be appreciated , Thank you

3 Replies 3

Based on the code you've shared,the client sends a single "join dashboard" message on connect (via socket.emit('dashboard-socket', { dbID: dashboardId })), and after that, it's purely server-to-client updates. No ongoing bidirectional communication, no client-initiated actions—just efficient, targeted pushes to subscribed dashboards. If that's accurate, you're overkill-ing it with Socket.IO. The full WebSocket stack adds unnecessary overhead: handshakes, pings, room management, and framing for two-way comms you don't need. I'd strongly recommend switching to Server-Sent Events (SSE) instead. SSE is designed exactly for this streaming one-way updates from server to browser over a long-lived HTTP connection. It's simpler, lighter, auto-reconnects on drops, and scales beautifully with your existing Redis Pub/Sub setup. No more looping over all sockets or global emits; just track active connections per dashboard and fan out precisely.

usefull reccourses : https://stackoverflow.com/questions/16096780/server-sent-events-with-multiple-users https://www.reddit.com/r/webdev/comments/149bjod/how_is_barely_anyone_talking_about_the_serversent/

@Rayan , Thanks for the SSE , you're actually Correct since I only emit from server , I never knew about this topic , I'm glad you told me that , Thanks , what do think will they handle efficiently , they told me as thousands of data hit per seconds I used reddis as to save incoming data and used a worker file to insert them in Mongodb

i think it will handle yes check this : https://station.railway.com/questions/are-there-limits-on-total-transfer-size-3c991de1

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.