Learn How to implement Real time Chat Application in Nextjs App Router
Derick Zr • August 07, 2023
7 min read
Learn How to implement Real time Chat Application in Nextjs App Router
Introduction
In this tutorial We will be using the power of Websockets to build a real time Chat App. At the end of the tutorial, you will have a basic understanding on how websockets work and how to create communication between your server and your frontend application to send and receive messages in real time.
Demo

In your root directory, create a new folder and name it wss
Folder structure:
.
├── ./build
├── ./LICENSE
├── ./node_modules
├── ./nodemon.json
├── ./package.json
├── ./package-lock.json
├── ./src
└── ./tsconfig.json
inside src we have:
.
├── ./data
├── ./lib
└── ./types
sub-folder:
.
├── ./data
│ ├── ./data/message.ts
│ └── ./data/user.ts
├── ./lib
│ └── ./lib/joinRoom.ts
├── ./server.ts
└── ./types
└── ./types/index.ts
To see what is inside of each files, the link to repository is here.
If all is set correctly, in your terminal go ahead a run the server npm run dev
you should see a message saying Server is running on port 3001 now
like this:
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/*
[nodemon] watching extensions: ts
[nodemon] starting `ts-node src/server.ts`
Server is running on port 3001 now!
FRONTEND
PS: This tutorial assumes you have a basic knowledge of React or Next.js.
The Design process
Here, I'll walk you through creating the required pages for the web application.
First of all, you need a homepage for your application. The homepage should have:
- Create Room: A section for user to create new chat room and get an access link that they can copy and share to others
- Join Room: A section where user can join a room if they have an access Key
Next, create a chat-room-page: this page is where people using the same room link will be chatting. A user can see how many people are in the room, get notified who joined and who leaves the room.cartoon by hacklescartoon by hackles
Since you have learnt how to build the pages of the application. Let's start writing some codes.
Connecting to the server
In order for the frontend to communicate with our server through websocket we need to make the connection by using a library called socket.io
For this article I'm using "socket.io version 4.7.1
to install the same version in package.json
in dependencies
add this line: "socket.io-client": "^4.7.1"
then go ahead and close the server then run npm install
after the installation finished re-run the server.
Next create a folder at the root of your Next.js called lib
inside create socket.ts
file and copy the socket configuration for your application:
import { io } from "socket.io-client"
const SERVER =
process.env.NODE_ENV === "production"
? "your-server-domain-name"
: "http://localhost:3001"
export const socket = io(SERVER, { transports: ["websocket"] })
Subscribing to socket events
Now navigate to the homepage directory of your application, and copy this:
import { socket } from "@/lib/sockets";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
...
const router = useRouter();
const setUser = useUserStore((state) => state.setUser);
const setMembers = useMembersStore((state) => state.setMembers);
useEffect(() => {
socket.on("room-joined", ({ user, roomId, members }: RoomJoinedData) => {
window.localStorage.setItem("admin", user.id);
setUser(user);
setMembers(members);
router.replace(`/${roomId}`);
});
function handleErrorMessage({ message }: { message: string }) {
console.log("Failed to join");
}
socket.on("room-not-found", handleErrorMessage);
socket.on("invalid-data", handleErrorMessage);
return () => {
socket.off("room-joined");
socket.off("room-not-found");
socket.off("invalid-data", handleErrorMessage);
};
}, [router, setUser, setMembers]);
This code first defines a function called handleRoomJoined
that is called when the room-joined
event is emitted by the socket. The handleRoomJoined
function takes a RoomJoinedData object as its argument, which contains information about the user who joined the room, the room ID, and the list of members in the room. The handleRoomJoined
function then sets the user's state, the members' state, and the router's location to reflect the fact that the user has joined the room.
At this point I created two separated components to handle the join and create boxes.
Create Room Component:
import { socket } from "@/lib/sockets";
import { nanoid } from "nanoid";
import React, { FormEvent, useEffect, useState } from "react";
...
const [userName, setUserName] = useState("");
const [error, setError] = useState<string | null>(null);
const [roomId, setRoomId] = useState<string>("");
//Generating the Room-ID
useEffect(() => {
setRoomId(nanoid());
}, []);
const createRoom = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError(null);
if (!roomId.trim() || !userName.trim())
return setError("You need to fill in your Name.");
socket.emit("create-room", { roomId, username: userName });
};
Here we check if the roomId
and userName
fields are empty. If they are empty, we return "You need to fill in your Name."
Otherwise, the function emits the create-room
event on the socket. The create-room
event takes a roomId
and username
as its arguments. The roomId
is the ID of the new room, and the username
is the username of the user who is creating the room.
Join Room Component:
const joinRoom = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
setError(null)
if (!roomId.trim() || !userName.trim())
return setError("You need to fill in all fields.")
socket.emit("join-room", { roomId, username: userName })
}
Same as the code for create-room
,
Here we are checking if the roomId
and userName
fields are empty. If they are empty, an error
state "You need to fill in all fields."
Otherwise, the function emits the join-room
event on the socket.
The socket.emit
method is used to emit an event on the socket. The join-room
event is the name of the event that is emitted. The { roomId, username: userName }
object is the data that is emitted with the event. The roomId
is the ID of the room that the user wants to join, and the username
is the username of the user who is joining the room.
Chat Room page
const [messageText, setMessageText] = useState("")
const [messagesList, setMessagesList] = useState<MessageObjData[]>([])
const lastItemRef = useRef<HTMLLIElement | null>(null)
const [getAdmin, setGetAdmin] = useState<string | null>("")
const [messages, setMessages] = useMessageStore((state) => [
state.message,
state.setMessage,
])
const messageList = useCallback((message: MessageObjData) => {
setMessagesList((prev) => [...prev, message])
}, [])
const roomId = usePathname().slice(1)
useEffect(() => {
setGetAdmin(localStorage.getItem("admin"))
}, [])
useEffect(() => {
socket.on("receive-message", messageList)
return () => {
socket.off("receive-message")
}
}, [messageList, messages, setMessages])
useEffect(() => {
if (messagesList.length === 0) return
lastItemRef.current?.scrollIntoView({ behavior: "smooth" })
}, [messagesList])
function onSubmit() {
setMessageText("")
socket.emit("send-message", {
roomId: roomId,
data: messageText,
type: "text",
})
}
The messageList
function is a callback function that is used to add a new message to the messagesList
variable. The useEffect
function is used to listen for the receive-message
event on the socket. The onSubmit
function is used to send a message to the chat room.
then uses the useEffect
hook to listen for the receive-message
event on the socket. When the receive-message
event is emitted, the function calls the messageList
function to add the new message to the messagesList
variable.
Leaving the Room
Create a leave the room
button to let the user leave the room if they need to. still in the chat-room
, copy this code:
useEffect(() => {
socket.on("update-members", (members) => {
setMembers(members)
})
socket.on("send-notification", ({ title, message }: Notification) => {
alert(`${title}, ${message}`)
})
return () => {
socket.off("update-members")
socket.off("send-notification")
}
}, [setMembers])
const leaveRoom = () => {
socket.emit("leave-room")
router.replace("/")
}
The sendNotification
function is called when the send-notification
event is emitted.
The hook then uses the useEffect
hook to listen for the update-members
and send-notification
events. When either event is emitted, the corresponding function is called.
The hook also defines a function called leaveRoom
. The leaveRoom
function is called when the user clicks the "Leave Room" button. The leaveRoom
function emits the leave-room
event on the socket and then redirects the user to the home page.