Real-time Notification System using Next.js and Socket.io
This article explains the process of creating a Real Time Notification System using Next.js and socket.io. In a typical scenario, imagine the need to create a user-friendly application. This application helps the user to send and receive notifications and can be used in multiple projects like E-commerce, chat applications, etc.
Output Preview: Let us have a look at how the final output will look like.
Prerequisite:
Approach to create a Real-time Notification:
- It receives real-time updates via a WebSocket connection using the socket.io-client library.
- app/page.js is a page component that uses the socket.io-client library to connect to the server and listen for notifications. When a notification is received, it is displayed on the page.
- app/layout.js is a wrapper for all pages in the app. It’s a good place to put shared components and styles.
- app/add/page.js is the page component for the add route. It is a React component that is rendered when the user navigates to /add. It contains a form that allows the user to add a new notification. When the form is submitted, the handleSubmit function is called, which sends a POST request to the server with the notification data.
- Establishes a WebSocket connection using io(‘http://localhost:4000’) via useMemo.
- Uses useEffect to handle connection events and attempts to reconnect in case of errors.
- Creates an Express server and a Socket.io server.
- server.js is the file that will handle the POST request from the client and emit the notification to the client.
- Handles WebSocket connections and updates in real-time.
- Logs user connections, emits initial data, and updates data on send notification.
Steps to create the NextJS Application
Step 1: Create a Next.js project using the following command:
npx create-next-app RealTime
cd RealTime
Step 2: Create a file server.js inside root directory.
Step 3: Install the required dependency in your server using the following command.
npm i express cors socket.io socket.io-client
Project Structure:
The updated dependencies in package.json file of backend will look like:
"dependencies": {
"axios": "^1.6.7",
"axos": "^0.0.1",
"cors": "^2.8.5",
"express": "^4.18.3",
"next": "14.1.1",
"react": "^18",
"react-dom": "^18",
"socket.io": "^4.7.4",
"socket.io-client": "^4.7.4"
},
"devDependencies": {
"autoprefixer": "^10.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.0"
}
Example: Add the following codesin the respective files.
//server.js
const express = require("express");
const app = express();
const PORT = 4000;
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
//New imports
const http = require("http").Server(app);
const cors = require("cors");
app.use(cors());
const socketIO = require('socket.io')(http, {
cors: {
origin: "*"
}
});
//Add this before the app.get() block
socketIO.on('connection', (socket) => {
console.log(`⚡: ${socket.id} user just connected!`);
socket.on('disconnect', () => {
console.log('?: A user disconnected');
});
});
app.post("/api", (req, res) => {
const { name, message } = req.body;
socketIO.emit('notification', { name, message });
console.log(name, message);
res.status(200).json({ name, message });
});
http.listen(PORT, () => {
console.log(`Server listening on ${PORT}`);
});
// app/page.js
"use client"
import { useEffect, useMemo, useState } from "react";
import socketio from "socket.io-client";
export default function Home() {
const socket = socketio.connect("http://localhost:4000")
useEffect(() => {
socket.on('connect', () => {
console.log(`Connected to server`);
})
socket.on('notification', (data) => {
console.log(`Notification from server`);
setNotifications([...notifications, data])
})
socket.on('disconnect', () => {
console.log(`Disconnected from server`);
})
}, [socket])
const [notifications, setNotifications] = useState([]);
return (
<main className="grid grid-cols-2 p-24 gap-6">
{
notifications ? notifications.map((notification, index) => {
return (
<div key={index} id="toast-message-cta"
className="w-full max-w-xs p-4 text-gray-500
bg-white rounded-lg shadow dark:bg-gray-800
dark:text-gray-400" role="alert">
<div className="flex">
<img className="w-8 h-8 rounded-full"
src="notification.png" alt="Jese Leos image" />
<div className="ms-3 text-sm font-normal">
<span className="mb-1 text-sm font-semibold
text-gray-900 dark:text-white">
{notification.name}</span>
<div className="mb-2 text-sm font-normal">
{notification.message}</div>
<a href="#" className="inline-flex px-2.5
py-1.5 text-xs font-medium text-center
text-white bg-blue-600 rounded-lg hover:bg-blue-700
focus:ring-4 focus:outline-none focus:ring-blue-300
dark:bg-blue-500 dark:hover:bg-blue-600
dark:focus:ring-blue-800">Reply</a>
</div>
<button type="button" className="ms-auto -mx-1.5
-my-1.5 bg-white justify-center items-center
flex-shrink-0 text-gray-400 hover:text-gray-900
rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5
hover:bg-gray-100 inline-flex h-8 w-8
dark:text-gray-500 dark:hover:text-white dark:bg-gray-800
dark:hover:bg-gray-700"
data-dismiss-target="#toast-message-cta"
aria-label="Close">
<span className="sr-only">Close</span>
<svg className="w-3 h-3" aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 14 14">
<path stroke="currentColor"
strokeLinecap="round" strokeLinejoin="round"
strokeWidth="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
</button>
</div>
</div>
)
}) : null
}
</main>
);
}
// app/layout.js
import Link from "next/link";
import "./globals.css";
export const metadata = {
title: "GFG Notifications",
description: "GFG Notifications - Send and receive notifications",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className="min-h-screen">
<nav className="bg-gray-200 shadow shadow-gray-300
w-100 px-8 md:px-auto">
<div className="md:h-16 h-28 mx-auto md:px-4
container flex items-center justify-between
flex-wrap md:flex-nowrap">
{/* Logo */}
<div className="text-indigo-500 md:order-1">
<img className="h-10" src="logo.png"
alt="Notifications" />
</div>
<div className="text-gray-500 order-3
w-full md:w-auto md:order-2">
<ul className="flex font-semibold
justify-between">
<li className="md:px-4 md:py-2
text-green-500">
<Link href="/">Home</Link>
</li>
<li className="md:px-4 md:py-2
hover:text-green-400">
<Link href="/add">
Send Notification
</Link>
</li>
</ul>
</div>
<div className="order-2 md:order-3">
<img className="h-6"
src="notification.png"
alt="Notifications" />
</div>
</div>
</nav>
{children}
</body>
</html>
);
}
// app/add/page.js
"use client"
import axios from "axios";
import { useState } from "react";
export default function Add() {
const handleSubmit = (e) => {
e.preventDefault();
const name = e.target[0].value;
const message = e.target[1].value;
axios.post("http://localhost:4000/api", { name, message })
.then((res) => {
console.log(res)
})
console.log("submitted", name, message)
}
return (
<main className="flex p-10 justify-center gap-6">
<div className="w-full max-w-xs">
<h2 className="text-center my-5 text-2xl">
Send New Notification
</h2>
<form className="bg-white shadow-md
rounded px-8 pt-6
pb-8 mb-4"
onSubmit={handleSubmit}>
<div className="mb-4">
<label
className="block text-gray-700
text-sm font-bold mb-2"
htmlFor="username"
>
Notification Title
</label>
<input
className="shadow appearance-none
border rounded
w-full py-2 px-3
text-gray-700 leading-tight
focus:outline-none
focus:shadow-outline"
type="text"
placeholder="Title"
/>
</div>
<div className="mb-6">
<label
className="block text-gray-700
text-sm font-bold mb-2"
>
Notification Message
</label>
<input
className="shadow appearance-none
border rounded w-full
py-2 px-3 text-gray-700
leading-tight
focus:outline-none
focus:shadow-outline"
type="test"
placeholder="Message"
/>
</div>
<div className="flex items-center
justify-between">
<button className="bg-blue-500
hover:bg-blue-700
text-white
font-bold py-2 px-4
rounded
focus:outline-none
focus:shadow-outline"
type="submit"
>
Send
</button>
</div>
</form>
</div>
</main>
);
}
Start your application using the following command.
node server.js // for socket io server
npm run dev // for nextJS app
Output: