Running multiple socket.io server in NextJS - reactjs

Hello after taking a look at the Chapter 7 of this article I think I need to run multiple socket.io server instances to efficiently use the resources of my server.
How can I achieve this? At the moment this is the following code of my socket.io server in NextJS:
import { Server } from "socket.io";
export default async function SocketHandler(req: any, res: any) {
if (res.socket.server.io) {
return res.end();
}
const io = new Server(res.socket.server);
res.socket.server.io = io;
io.on("connection", async (socket) => {
socket.on("disconnect", async () => {
io.socket.emit("user-disconnected");
});
});
res.end();
}

Related

Working with websocket in ReactJS project

I am tying to implement socket connection in ReactJs, I have read there documentation for how to implement more than one namespace in socket and this is how I did it after following there instructions
import {Manager} from "socket.io-client";
export const manager = new Manager(process.env.REACT_APP_SOCKET_API, {
transports: ["websocket"],
reconnection:true,
autoConnect:true,
});
// add the namespace [namespace1,namespace2]
export const chatSocket = manager.socket("/namespace1");
export const notesSocket = manager.socket("/namespace2");
manager.open((err) => {
if (err) {
console.log("socket connection error", err);
} else {
console.log("socket connection succeeded");
}
});
now I am trying to use it like that in my react app is this right or I should use it in another way because I don't see any result from my console.log mehtod
useEffect(() => {
chatSocket.on('connect',() => {
console.log('connected socket chat')
})
notesSocket.on('connect',() => {
console.log('connected socket notes')
})
},[])

MongoDB & Next.js – two connections at the same time = Error: Cannot use a session that has ended

I'm creating an application in Next.js and I'm at a stage where I need to load data from two different API endpoints that connect to MongoDB.
Specifically:
I have an index.js file that contains two components.
DashMenu.js (Component that displays basic information)
LunchMenuEditor.js (Component in which there is a textarea and the text of the lunch menu changes)
The problem occurs when I need to retrieve data from the address .../api/specials in the DashMenu component using useEffect() and at the same time the LunchMenuEditor component is loaded, which needs to retrieve data from the address .../api/[week].js?year=2022.
The result is an error: "MongoExpiredSessionError: Cannot use a session that has ended".
In my opinion there is an error in db.connect() and disconnect() in api files (see code). If I delete db.disconnect(), everything works, but the connection remains open.
I also tried db.connect() to insert it in setTimeout() with a wait of 3 seconds, it worked, but it occurs to me that it is not professional and I would be concerned about long-term sustainability.
So my questions are:
How to solve the problem "MongoExpiredSessionError: Cannot use a session that has ended"
Will it matter if the connection to MongoDB remains open? Does the connection end automatically after some time?
Thank you for every answer 🙂
Codes:
.../api/[week]:
import db from "../../../utils/db";
import LunchMenu from "../../../models/LunchMenu";
export default async (req, res) => {
await db.connect();
if (req.method === "GET") {
const lunchMenu = await LunchMenu.find({ week: 4, year: 2022 });
res.json(lunchMenu);
}
await db.disconnect();
};
.../api/specials:
import db from "../../../utils/db";
import Specials from "../../../models/Specials";
export default async (req, res) => {
await db.connect();
const specials = await Specials.find({ visible: true });
await db.disconnect();
res.json(specials);
};
utils/db.js
import mongoose from "mongoose";
const connection = {};
async function connect() {
if (connection.isConnected) {
console.log("Already connected.");
return;
}
if (mongoose.connection.length > 0) {
connection.isConnected = mongoose.connections[0].readyState;
if (connection.isConnected === 1) {
console.log("Uses previous connections.");
return;
}
await mongoose.disconnect();
}
const db = await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log("A new connection has been established.");
connection.isConnected = db.connections[0].readyState;
}
async function disconnect() {
if (connection.isConnected) {
await mongoose.disconnect();
connection.isConnected = false;
console.log("Disconnected.");
}
}
const db = { connect, disconnect };
export default db;

Socket.io and Next.Js

I am working on a project for an interview and have been asked to create a NextJS app using Socket.io for realtime chat. I have the chat functionality working, but one of my requirements is to have an area where a user can see a list of current users. While I've found examples for Express servers, I cannot seem to work out how to do this using Next's API system. I have two connected issues:
Maintaining a list of users with chosen display names (not just the socket id)
Accessing and returning a current user list whenever a user joins or leaves.
I haven't had any luck scanning the docs.
Here is the server function:
import { NextApiRequest } from 'next';
import { NextApiResponseServerIO } from '../../types/next';
import { Server as ServerIO } from 'socket.io';
import { Server as NetServer } from 'http';
export const config = {
api: {
bodyParser: false
}
}
export default async (req: NextApiRequest, res: NextApiResponseServerIO) => {
if (!res.socket.server.io) {
console.log("** New Socket.io server **")
// adapts the Next net server to http server
const httpServer: NetServer = res.socket.server as any;
const io = new ServerIO(httpServer, {
path: '/api/socketio'
})
io.on('connect', async (socket) => {
socket.join('main')
// where I plan to put the code to send a current list
})
io.on('disconnect', socket => {
socket.leave('main')
})
res.socket.server.io = io;
}
res.end();
}
And the related client code:
useEffect((): any => {
const url = process.env.NEXT_BASE_URL as string | "";
// connect to socket server
const socket = io(url, {
path: "/api/socketio",
});
// log socket connection
socket.on("connect", () => {
dispatch(connect(socket.id));
dispatch(updateId(socket.id))
});
//updates chat on message dispatch
socket.on("message", (message: IMsg) => {
dispatch(receive(message));
});
socket.on('updateUsersList', (users) => {
console.log("Is this the users", users)
})
//server disconnect on unmount
if (socket) return () => dispatch(disconnect(socket));
}, []);

FormData with NextJS API

Background
I am trying to create a simple CRUD application using NextJS along with react-redux, so what it does is that it saves peoples contacts.So when adding a contact i am trying to send some data along with a file to a NextJS API.
Issue
ContactAction.js
Make a POST request from redux action to add a contact
export const addContact = (data) => async (dispatch) => {
try {
var formData=new FormData();
formData.append('name',data.Name);
formData.append('email',data.Email);
formData.append('phone',data.Phone);
formData.append('image',data.Image);
let response= await Axios.post(`http://localhost:3000/api/contact/addContact`,formData,{
headers:{
'x-auth-token':localStorage.getItem('token')
}
});
} catch (error) {
console.log(error);
}
}
addContact.js
This is the API route in /api/contact/
const handler = async (req, res) => {
switch(req.method){
case "POST":{
await addContact(req,res)
}
}
}
const addContact = async (req, res) => {
console.log(req.body);
// do some stuff here and send response
}
this is what i get in the terminal after the log,also the file is Gibberish as well when logging req.files
Current Effort
I tried using third party packages such as formidable and formidable-serverless but got no luck. so after a day i made it work with a package called multiparty.
addContact.js
const handler = async (req, res) => {
switch(req.method){
case "POST":{
let form = new multiparty.Form();
let FormResp= await new Promise((resolve,reject)=>{
form.parse(req,(err,fields,files)=>{
if(err) reject(err)
resolve({fields,files})
});
});
const {fields,files} = FormResp;
req.body=fields;
req.files=files;
await addContact(req,res)
}
}
}
const addContact = async (req, res) => {
console.log(req.body); //Now i get an Object which i can use
// do some stuff here and send response
}
The above solution is obviously redundant and probably not the best way to go about it plus i don't want to add these 7 8 lines into each route.
so if someone could help me understand what i am doing wrong and why formData doesn't seem to work with NextJS API (when it works with the Express server) i would be grateful.
FormData uses multipart/form-data format. That is not a simple POST request with a body. It is generally used for uploading files, that's why it needs special handling. As an alternative, you could use JSON.
Here is my solution, i hope this helps anybody.
First of all you need to install next-connect and multer as your dependencies.
Now you can use this API route code.
import nextConnect from "next-connect";
import multer from "multer";
const apiRoute = nextConnect({
onError(error, req, res) {
res.status(501).json({ error: `Sorry something Happened! ${error.message}` });
},
onNoMatch(req, res) {
res.status(405).json({ error: `Method "${req.method}" Not Allowed` });
},
});
apiRoute.use(multer().any());
apiRoute.post((req, res) => {
console.log(req.files); // Your files here
console.log(req.body); // Your form data here
// Any logic with your data here
res.status(200).json({ data: "success" });
});
export default apiRoute;
export const config = {
api: {
bodyParser: false, // Disallow body parsing, consume as stream
},
};
Here is an example about uploading file with Next.js:
https://codesandbox.io/s/thyb0?file=/pages/api/file.js
The most important code is in pages/api/file.js
import formidable from "formidable";
import fs from "fs";
export const config = {
api: {
bodyParser: false
}
};
const post = async (req, res) => {
const form = new formidable.IncomingForm();
form.parse(req, async function (err, fields, files) {
await saveFile(files.file);
return res.status(201).send("");
});
};
const saveFile = async (file) => {
const data = fs.readFileSync(file.path);
fs.writeFileSync(`./public/${file.name}`, data);
await fs.unlinkSync(file.path);
return;
};
Generally speaking,in your api file,you should disable the default bodyParser,and write your own parser

How to avoid reconnections with socket.io-client and React app?

I tried to connect React client to my Socket.IO server. I noticed Socket.IO client reconnects every +/- 5s. When I try do the same thing with vanilla html/js simple app everything works crrectly.
Inside React component:
useEffect(() => {
const s = getChatClient();
}, []);
Inside socket.io-client module:
var chatClient;
export function getChatClient(
token = "secret"
) {
if (!chatClient) {
chatClient = io("http://localhost:5000/chat", {
query: {
token,
},
});
chatClient
.on("connect", () => {
chatClient.emit("textMessage", "123cos");
})
.on("hello", (msg) => {
console.log("12");
});
}
return chatClient;
}
BTW: I know I can do it export const etc (I've changed to this version becouse I thought it'll help).
I tried different ways to resolve this problem, but I got in stuck. Any ideas?
Log from the server when I open html/js simple client:
15:30:00 User Ilona connected to the socket.io server on /
and when I quit:
15:29:12 User Ilona disconnected
With React App:
15:30:00 User Ilona connected to the socket.io server on '/'
15:30:05 User Ilona disconnected
15:30:10 User Ilona connected to the socket.io server on '/'
15:30:15 User Ilona disconnected
15:30:20 User Ilona connected to the socket.io server on '/'
15:30:25 User Ilona disconnected
The problem isn't related with component rerender or something like this.
I'm working on MacOS Big Sur.
Consider creating, and then consuming from a context:
SocketContext.jsx :
import { createContext, useState } from 'react';
export const SocketContext = createContext();
export default function SocketContextProvider(props) {
const [sock, setSocket] = useState(null);
let socket = async () => {
if (sock) {
return Promise.resolve(sock); // If already exists resolve
}
return new Promise((resolve, reject) => {
let newSock = io('URL'),
{
query: {
// Options
},
}; // Try to connect
let didntConnectTimeout = setTimeout(() => {
reject();
}, 15000) // Reject if didn't connect within 15 seconds
newSock.once('connect', () => {
clearTimeout(didntConnectTimeout); // It's connected so we don't need to keep waiting 15 seconds
setSocket(newSock); // Set the socket
resolve(newSock); // Return the socket
})
});
};
return (
<SocketContext.Provider value={{ socket }}>
{props.children}
</SocketContext.Provider>
);
}
Component.jsx :
import { useContext, useState, useEffect } from 'react';
import { SocketContext } from './SocketContext.jsx';
export default function MyComponent() {
const { socket } = useContext(SocketContext);
const [sock, setSock] = useState(null);
useEffect(() => {
socket()
.then((resultSocket) => setSock(resultSocket))
.catch(() => {
/* Catch any errors here */
console.log('Couldn\'t connect the socket!')
});
}, []);
return (
<div>
<code>I'm a context consumer...</code>
</div>
);
}

Resources