💬 Building a real-time chat with Websockets, Novel and Clerk 🚀🚀
Tags: webdev, react, javascript, tutorial
Published: 2023-09-07 13:38:54

# TL;DR
In this tutorial, you'll learn how to build a chat application.

**On the agenda** 🔥:
- Create accounts and send real-time messages using React.js, Node.js and Websockets.
- Build an authentication with Clerk.
- Add a rich-text editor for the chat with Novel.

![Chatting](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kkqfzdoqpjrjecn3zqu5.gif)

<hr />


## Novu: Open-source notification infrastructure 🚀

Just a quick background about us. Novu is an open-source [notification infrastructure](https://novu.co). We basically help to manage all the product notifications. It can be **In-App** (the bell icon like you have in the Dev Community), Emails, SMSs and so on.

[![Like](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bojvpv96zk4noelozjjc.png)
](https://github.com/novuhq/novu)

https://github.com/novuhq/novu

<hr />

## Let's set it up 🚀

[Socket.io](http://socket.io/) is a popular JavaScript library that allows us to create real-time, bi-directional communication between web browsers and a Node.js server. It is a highly performant and reliable library optimised to process a large volume of data with minimal delay.

Here, you'll learn how to add Socket.io to a React and Node.js application and connect both development servers for real-time communication.

Create a folder for the web application as done below.

```bash
mkdir chat-app
cd chat-app
mkdir client server
```

Navigate into the client folder via your terminal and create a new React.js project with [Vite](https://vitejs.dev/).

```bash
npm create vite@latest
```

Install Socket.io client API and React Router. [React Router](https://reactrouter.com/en/main) is a JavaScript library that enables us to navigate between pages in a React application.

```bash
npm install socket.io-client react-router-dom
```

Delete the redundant files such as the logo and the test files from the React app, and update the `App.jsx` file to display “Hello World” as below.

```jsx
function App() {
	return (
		<div>
			<p>Hello World!</p>
		</div>
	);
}
```

Copy the [CSS file](https://github.com/novuhq/blog/blob/main/chat-app-with-websockets-novel/client/src/index.css) required for styling the project into the `src/index.css` file.

### Connecting the React app to the Node.js server

Run the code snippet below to create a `package.json` file within the server folder.

```bash
cd server
npm init -y
```

Install Express.js, CORS, Nodemon, and Socket.io Server API.

[Express.js](https://expressjs.com/) is a fast, minimalist framework that provides several features for building web applications in Node.js. [CORS](https://www.npmjs.com/package/cors) is a Node.js package that allows communication between different domains.

[Nodemon](https://www.npmjs.com/package/nodemon) is a Node.js tool that automatically restarts the server after detecting file changes, and [Socket.io](https://socket.io/docs/v4/server-api) allows us to configure a real-time connection on the server.

```bash
npm install express cors nodemon socket.io
```

Create an `index.js` file - the entry point to the web server.

```bash
touch index.js
```

Set up a simple Node.js server using Express.js. The code snippet below returns a JSON object when you visit the `http://localhost:4000/api` in your browser.

```jsx
//👇🏻 index.js
const express = require("express");
const app = express();
const PORT = 4000;

app.get("/api", (req, res) => {
	res.json({
		message: "Hello world",
	});
});

app.listen(PORT, () => {
	console.log(`Server listening on ${PORT}`);
});
```

Import the HTTP and the CORS library to allow data transfer between the client and the server domains.

```jsx
const express = require("express");
const app = express();
const PORT = 4000;

//👇🏻 New imports
const http = require("http").Server(app);
const cors = require("cors");

app.use(cors());

app.get("/api", (req, res) => {
	res.json({
		message: "Hello world",
	});
});

http.listen(PORT, () => {
	console.log(`Server listening on ${PORT}`);
});
```

Next, add [Socket.io](http://socket.io/) to the project to create a real-time connection. Before the `app.get()` block, copy the code below.

```jsx
//👇🏻 New imports
const socketIO = require("socket.io")(http, {
	cors: {
		origin: "http://localhost:5173",
	},
});

//👇🏻 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");
	});
});
```

From the code snippet above, the `socket.io("connection")` function establishes a connection with the React app, creates a unique ID for each socket, and logs the ID to the console whenever a user visits the web page.

When you refresh or close the web page, the socket fires the disconnect event to show that a user has disconnected from the socket.

Next, configure Nodemon by adding the start command to the list of the scripts in the `package.json` file. The code snippet below starts the server using Nodemon.

```json
//👇🏻In server/package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon index.js"
  },
```

You can now run the server with Nodemon by using the command below.

```bash
npm start
```

Open the `App.jsx` file in the client folder and connect the React app to the Socket.io server.

```jsx
import socketIO from "socket.io-client";
const socket = socketIO.connect("http://localhost:4000");

function App() {
	return (
		<div>
			<p>Hello World!</p>
		</div>
	);
}
```

Start the React.js server by running the code snippet below.

```bash
npm run dev
```

Check the server's terminal; the ID of the React.js client will be displayed. Congratulations 🥂 , you've successfully connected the React app to the server via Socket.io.

<hr />

## Adding authentication to your app 👤

[Clerk](https://github.com/clerkinc) is a complete user management package that enables you to add various forms of authentication to your software applications. With Clerk, you can authenticate users via password and password-less sign-in, social account login, SMS verification, and Web3 authentication.

Clerk also provides prebuilt authentication components that enable you to authenticate users easily and focus more on the application's logic. These components are also customisable.

In this article, I'll walk you through

- adding Clerk to a React app,
- authenticating users with Clerk,
- sending real-time messages with [Socket.io](https://socket.io/), and
- adding novel text editor to a React application.

### Adding Clerk to your App

Here, you'll learn how to authenticate users via Clerk. Before we proceed, create a [Clerk account](https://clerk.com/).

Create a new Clerk application, as shown below.


![clerk](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h3ofl3v2778rdaemaogg.png)


Copy your Publishable key into a `.env` file within your React app.


```bash
VITE_REACT_APP_CLERK_PUBLISHABLE_KEY=<your_publishable_key>
```

![Publishable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qal9nvxa2uqcyweb6wa0.png)

Finally, update the `App.jsx` file to display the `Signup` and `Signin` UI components provided by Clerk.

```jsx
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import socketIO from "socket.io-client";
import {
	ClerkProvider,
	SignedIn,
	SignedOut,
	SignIn,
	SignUp,
	RedirectToSignIn,
} from "@clerk/clerk-react";

//👇🏻 gets the publishable key
const clerkPubKey = import.meta.env.VITE_REACT_APP_CLERK_PUBLISHABLE_KEY;

//👇🏻 socketIO configuration
const socket = socketIO.connect("http://localhost:4000");

const App = () => {
	return (
		<Router>
			<ClerkProvider publishableKey={clerkPubKey}>
				<Routes>
					<Route
						path='/*'
						element={
							<div className='login'>
								<SignIn
									path='/'
									routing='path'
									signUpUrl='/register'
									afterSignInUrl='/chat'
								/>{" "}
							</div>
						}
					/>

					<Route
						path='/register/*'
						element={
							<div className='login'>
								<SignUp afterSignUpUrl='/chat' />
							</div>
						}
					/>

					<Route
						path='/chat'
						element={
							<>
								<SignedIn>
									<Home socket={socket} />
								</SignedIn>
								<SignedOut>
									<RedirectToSignIn />
								</SignedOut>
							</>
						}
					/>
				</Routes>
			</ClerkProvider>
		</Router>
	);
};

export default App;
```

- From the code snippet above,
    - You need to wrap the entire application with the `ClerkProvider` component and pass the publishable key as a prop into the component.
    - The [SignIn](https://clerk.com/docs/components/authentication/sign-in) and [SignUp](https://clerk.com/docs/components/authentication/sign-up) components enable you to add [Clerk's](https://clerk.com/docs/quickstarts/react) authentication components to your React app.
    - The `Home` component is a protected route which is only available to authenticated users.


![Clerk](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tcmubw7q7y4hpw987nz2.gif)


Create a components folder containing the `Home.jsx` file. This will be homepage for the chat application.

```bash
cd client/src
mkdir components
touch Home.jsx
```

![Home](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3vl4hlkzlkapi0rdlhw.png)

Copy the code below into the `Home.jsx` file to replicate the chat UI above.

```jsx
import { useState } from "react";
import { Link } from "react-router-dom";
import { SignOutButton, useAuth } from "@clerk/clerk-react";

const Home = ({ socket }) => {
	const { isLoaded, userId } = useAuth();
	const [write, setWrite] = useState(false);
	const writeFunction = () => setWrite(true);

	const handleSubmit = () => {
		console.log({ message: "Submit Clicked!", userId });
		setWrite(false);
	};

	// In case the user signs out while on the page.
	if (!isLoaded || !userId) {
		return null;
	}

	return (
		<div>
			<nav className='navbar'>
				<Link to='/' className='logo'>
					Mingle
				</Link>
				<SignOutButton signOutCallback={() => console.log("Signed out!")}>
					<button className='signOutBtn'>Sign out</button>
				</SignOutButton>
			</nav>

			{!write ? (
				<main className='chat'>
					<div className='chat__body'>
						<div className='chat__content'>
							{/**-- contains chat messages-- */}
						</div>
						<div className='chat__input'>
							<div className='chat__form'>
								<button className='createBtn' onClick={writeFunction}>
									Write message
								</button>
							</div>
						</div>
					</div>
					<aside className='chat__bar'>
						<h3>Active users</h3>
						<ul>
							<li>David</li>
							<li>Dima</li>
						</ul>
					</aside>
				</main>
			) : (
				<main className='editor'>
					<header className='editor__header'>
						<button className=' editorBtn' onClick={handleSubmit}>
							SEND MESSAGE
						</button>
					</header>

					<div className='editor__container'>Your editor container</div>
				</main>
			)}
		</div>
	);
};

export default Home;
```

From the code snippet above, Clerk provides a `SignOutButton` component and a `useAuth` hook. The `SignOutButton` component can be wrapped around a custom button tag, and the `useAuth` hook enables us to access the current user's ID. We'll use the user's ID for identifying users on the Node.js server.

When users click the "Write message" button, the UI changes to the Editor screen. In the upcoming section, you'll learn how to add the Novel text editor to a React app.


![Write a message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/it7hihmqudolau46meja.gif)


<hr />

## Adding the next-gen editor to our chat 💬

[Novel](https://novel.sh/) is a Notion-style WYSIWYG editor that supports various text formats and image upload. It also provides AI auto-completion.

Install Novel by running the code snippet below.

```bash
npm install novel
```

Import the Editor component into the `Home.jsx` component.

```jsx
import { Editor } from "novel";
import "novel/styles.css";
```

Add the Editor component to the UI as shown below.

Add the Editor component to the UI as shown below.

```jsx
<div>
		<main className='editor'>
			<header className='editor__header'>
				<button className=' editorBtn' onClick={handleSubmit}>
					SEND MESSAGE
				</button>
			</header>

			<div className='editor__container'>
                {/**-- 👇🏻 Editor component --**/}
				<Editor onUpdate={(e) => setValue(updateMessage(e.content))} />
			</div>
		</main>
</div>
```

Add the code snippet below to the component to access the user’s input.

```jsx
//👇🏻 holds the Editor's content
const [value, setValue] = useState([]);

//👇🏻 saves only the heading and paragraph texts
const updateMessage = (array) => {
	const elements = [];
	for (let i = 0; i < array.length; i++) {
		if (array[i].type === "paragraph" || array[i].type === "heading") {
			elements.push(array[i].content[0].text);
		}
	}
	return elements.join("\n");
};
```

<hr />

## Handle real-time communication 👨‍👨‍👧

Here, you'll learn how to send the user's messages to the Node.js server and also view online users.

Create a `handleSubmit` function that sends the Editor's content and the user's ID to the server when the user clicks the `Send Message` button.

```jsx
const handleSubmit = () => {
	socket.emit("message", {
		value,
		userId: userId.slice(0, 10),
	});
	setWrite(false);
};
```

Update the Socket.io listener on the Node.js server to listen to the `message` event.

```jsx
socketIO.on("connection", (socket) => {
	console.log(`⚡: ${socket.id} user just connected!`);
	//👇🏻 receives the data from the React app
	socket.on("message", (data) => {
		console.log(data);
		socketIO.emit("messageResponse", data);
	});

	socket.on("disconnect", () => {
		console.log("🔥: A user disconnected");
	});
});
```

The code snippet above receives the data from the `message` event and sends the message back to the React app to display its content.

Add the event listener to the React app.

```jsx
//👇🏻 holds online users
const [onlineUsers, setOnlineUsers] = useState([]);
//👇🏻 holds all the messages
const [messages, setMessages] = useState([]);

useEffect(() => {
	socket.on("messageResponse", (data) => {
		setMessages([...messages, data]);
		if (!onlineUsers.includes(data.userId)) {
			setOnlineUsers([...onlineUsers, data.userId]);
		}
	});
}, [socket, messages, onlineUsers]);
```

The code snippet above adds the new message to the `messages` array and updates the `onlineUsers` state if the user's ID is not on the list.

### Scroll down on a new message 🆕

In this section, you'll learn how to move the scrollbar to the most recent message when there is a new message.


![Scroll](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/970e78696m5u0bfwblje.gif)


Create a new ref with the `useRef` hook that scrolls the bottom of the messages container.

```jsx
import { useRef, useEffect } from "react";
const lastMessageRef = useRef(null);

useEffect(() => {
	// 👇️ scroll to bottom every time messages change
	lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
```

Add a div element with a ref attribute, as shown below.

```jsx
<div>
	{/** --- messages container ---*/}
	<div ref={lastMessageRef} />
</div>
```

When there is a new message, the scrollbar focuses on the latest message. 

Congratulations!🎉 You've completed this project.

<hr />

## Conclusion

So far, you've learnt how to authenticate users with Clerk, send real-time messages via Socket.io in a React and Node.js application, and add Novel WYSIWYG editor to a React app.

Socket.io is an excellent tool for building efficient applications that require real-time communication, and [Clerk](https://github.com/clerkinc) is a great authentication management system that provides all forms of authentication. It is also open-source - you can request customized features or contribute to the tool as a developer.

The source code for this tutorial is available here:

https://github.com/novuhq/blog/tree/main/chat-app-with-websockets-novel

Thank you for reading!

<hr />

[![Like](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bojvpv96zk4noelozjjc.png)
](https://github.com/novuhq/novu)