Real-time Chat application under 5 minutes using NEXTJS and Socket.io

Chat with your global friends or school friends.

Sarath Adhithya
5 min readDec 3, 2022

Let’s start the timer,

1. First, create a next app

npx create-next-app@latest

After creating the Next app, our project structure looks something like this.

Project structure

2. Install the following dependencies

npm i socket.io socket.io-client
Dependencies

3. Now let's start to code,

go to pages/index.js file and copy the basic template.

import React, { useEffect, useState } from "react";
import io from "socket.io-client";

let socket;

const Home = () => {
useEffect(() => {
socketInitializer();

return () => {
socket.disconnect();
};
}, []);

async function socketInitializer() {
}

function handleSubmit(e) {
}

return (
<div>
<h1>Chat app</h1>
</div>
);
};

export default Home;

Whenever the component is mounted, socketInitializer() function is called and whenever the component is unmounted, we are disconnecting the socket connection. But where is the socket connection happening?

Before that let's set up the backend, here we use Nextjs which allows you to create APIs endpoints in a pages directory as though you’re writing backend code.

In pages/api we will create a new file called socket.js.

pages/api/socket.js
import { Server } from "socket.io";

export default function SocketHandler(req, res) {
const io = new Server(res.socket.server);
res.socket.server.io = io;

io.on("connection", (socket) => {
// after the connection.....
});

console.log("Setting up socket");
}

Now, when the client connects from the front end, the backend captures the connection even, with that we are creating a new socket.io server and assigning it to res.socket.server.io.

// Waits for a socket connection
io.on("connection", socket => {
// Each socket has a unique ID
// console.log(socket.id) -> ojIckSD2jqNzOqIrAGzL
})

Improving the code.

import { Server } from "socket.io";

export default function SocketHandler(req, res) {
if (res.socket.server.io) {
console.log("Already set up");
res.end();
return;
}

const io = new Server(res.socket.server);
res.socket.server.io = io;

io.on("connection", (socket) => {
socket.on("send-message", (obj) => {
io.emit("receive-message", obj);
});
});

console.log("Setting up socket");
res.end();
}

Now here, if a res.socket.server.io exist, then no need to create the socket.io server again, so we are simpling returning.

Inside the socket connection, we have created a listening event.

So what it does is, Adds the listener function to the end of the listener's array for the event named send-message (we call it as emit). So whenever a emit happens from the frontend with the event named send-message, then this event gets executed.

Inside it, we have an emitter named receive-message, which will emit the received object (obj) to the client side.

Now back to the client side, let’s connect to the backend.

import React, { useEffect, useState } from "react";
import io from "socket.io-client";

let socket;

const Home = () => {

useEffect(() => {
socketInitializer();

return () => {
socket.disconnect();
};
}, []);

async function socketInitializer() {
await fetch("/api/socket");

socket = io();

socket.on("receive-message", (data) => {
// we get the data here
});
}

function handleSubmit(e) {
}

return (
<div>
<h1>Chat app</h1>
</div>
);
};

export default Home;

We have initialized the socket by using the fetch(). This will create the socket server.

Next, we have an event listener which will listen if any emit is happening in the name receive-message.

Let's create a simple UI

import React, { useEffect, useState } from "react";
import io from "socket.io-client";

let socket;

const Home = () => {
const [message, setMessage] = useState("");
const [username, setUsername] = useState("");

useEffect(() => {
socketInitializer();

return () => {
socket.disconnect();
};
}, []);

async function socketInitializer() {
await fetch("/api/socket");

socket = io();

socket.on("receive-message", (data) => {
//
});
}

function handleSubmit(e) {
}

return (
<div>
<h1>Chat app</h1>
<h1>Enter a username</h1>

<input value={username} onChange={(e) => setUsername(e.target.value)} />

<br />
<br />

<div>
<form onSubmit={handleSubmit}>
<input
name="message"
placeholder="enter your message"
value={message}
onChange={(e) => setMessage(e.target.value)}
autoComplete={"off"}
/>
</form>
</div>
</div>
);
};

export default Home;
Simple UI

Username and a message box, the message box input field is wrapped inside the form. Whenever the form is submitted, we are calling the handleSubmit function.

Now let’s emit the message

import React, { useEffect, useState } from "react";
import io from "socket.io-client";

let socket;

const Home = () => {
const [message, setMessage] = useState("");
const [username, setUsername] = useState("");
const [allMessages, setAllMessages] = useState([]);

useEffect(() => {
socketInitializer();

return () => {
socket.disconnect();
};
}, []);

async function socketInitializer() {
await fetch("/api/socket");

socket = io();

socket.on("receive-message", (data) => {
setAllMessages((pre) => [...pre, data]);
});
}

function handleSubmit(e) {
e.preventDefault();

console.log("emitted");

socket.emit("send-message", {
username,
message
});

setMessage("");
}

return (
<div>
<h1>Chat app</h1>
<h1>Enter a username</h1>

<input value={username} onChange={(e) => setUsername(e.target.value)} />

<br />
<br />

<div>
{allMessages.map(({ username, message }, index) => (
<div key={index}>
{username}: {message}
</div>
))}

<br />

<form onSubmit={handleSubmit}>
<input
name="message"
placeholder="enter your message"
value={message}
onChange={(e) => setMessage(e.target.value)}
autoComplete={"off"}
/>
</form>
</div>
</div>
);
};

export default Home;

Inside the handleSubmit function, we have an emitter, that will emit a username and a message as an object to the send-message event listener in the backend. After it reaches the backend, we have another emitter inside the event listener (receive-message), which will emit back to the frontend.

Now the receive-message event listener gets the data.

socket.on("receive-message", (data) => {
setAllMessages((pre) => [...pre, data]);
});

All the above function happens Real-time to all the user’s currently using the application.

Go and play around with the code. Try creating a room for the user, refer to this doc.

All the codes are the in my GitHub. Check them out and STAR the repository if you have successfully completed the Chat Application.

Like and wait for an awesome project with socket.io and Nextjs.

Spoiler alert, the next app would be a game.

--

--

Sarath Adhithya
Sarath Adhithya

Written by Sarath Adhithya

Full Stack Developer | Blockchain Enthusiast | Frontend ❤️ | MERN | NEXTJS | REACTJS