# Building a forum with React, NodeJS

> In this article, you'll learn how to build a forum system that allows users to create, react, and reply to post threads.

Canonical: https://novu.co/blog/building-a-forum-with-react-nodejs/

Markdown: https://novu.co/blog/building-a-forum-with-react-nodejs.md

Last updated: 2023-02-27T18:48:08.000Z

In this article, you'll learn how to build a forum system that allows users to create, react, and reply to post threads. In the end, we will also send a notification on each reply on a thread with Novu, you can skip the last step if you want only the…

Authors: Nevo David

Published: 2023-02-27T18:48:08.000Z

Category: How to

I know, there are no forums today like before, it’s all about the Reddit, Facebook communities (and smaller ones like Devto and Mastodon), but! Once upon a time, when I was a boy, I was addicted to forums, I have actually created a few with [PHPBB](<https://www.phpbb.com/>) and [vBulletin](<https://www.vbulletin.com/>), back when PHP was a thing. being so nostalgic made me write this blog post 😎

## A small request 🤗

I am trying to get Novu to **20k stars**, can you help me out by starring the repository? it helps me to create more content every week.

[https://github.com/novuhq/novu](<https://github.com/novuhq/novu>)

## Project Setup

Here, I’ll guide you through creating the project environment for the web application. We’ll use React.js for the front end and Node.js for the backend server.

Create the project folder for the web application by running the code below:

```bash
mkdir forum-system
cd forum-system
mkdir client server
```

### Setting up the Node.js server

Navigate into the server folder and create a `package.json` file.

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

Install Express, Nodemon, and the CORS library.

```bash
npm install express cors nodemon
```

[ExpressJS](<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, and [Nodemon](<https://www.npmjs.com/package/nodemon>) is a Node.js tool that automatically restarts the server after detecting file changes.

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

```bash
touch index.js
```

Set up a 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.

```javascript
//\ud83d\udc47\ud83c\udffbindex.js
const express = require("express");
const cors = require("cors");
const app = express();
const PORT = 4000;

app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(cors());

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

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

Configure Nodemon by adding the start command to the list of 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"
  },
```

Congratulations! You can now start the server by using the command below.

```bash
npm start
```

### Setting up the React application

Navigate into the client folder via your terminal and create a new React.js project.

```bash
cd client
npx create-react-app ./
```

Install [React Router](<https://reactrouter.com/en/main>) – a JavaScript library that enables us to navigate between pages in a React application.

```bash
npm install react-router-dom
```

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

```jsx
function App() {
    return (

            Hello World!</p>
        </div>
    );
}
export default App;
```

Copy the CSS file required for styling the project [here](<https://github.com/novuhq/blog/blob/main/forum-system-with-react-novu-node/client/src/index.css>)  into the `src/index.css` file.

## Building the app user interface

Here, we’ll create the user interface for the forum system to enable users to create, reply and react to various threads.

Create a components folder within the `client/src` folder containing the `Home.js`, `Login.js`, `Nav.js`, `Register.js`, and `Replies.js` files.

```bash
cd client/src
mkdir components
touch Home.js Login.js Nav.js Register.js Replies.js
```

- From the code snippet above:

  - The `Login.js` and `Register.js` files are the authentication pages of the web application.

  - The `Home.js` file represents the dashboard page shown after authentication. It allows users to create and react to the post threads.

  - The `Replies.js` file displays the response on each post and allows users to reply to the post thread.

  - The `Nav.js` is the navigation bar where we will configure Novu.

Update the `App.js` file to render the components using React Router

```jsx
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Register from "./components/Register";
import Login from "./components/Login";
import Home from "./components/Home";
import Replies from "./components/Replies";

const App = () => {
    return (

                    } />
                    } />
                    } />
                    } />
                </Routes>
            </BrowserRouter>
        </div>
    );
};

export default App;
```

### The Login page

Copy the code below into the `Login.js` file to render a form that accepts the user’s email and password.

```jsx
import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";

const Login = () => {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log({ email, password });
        setEmail("");
        setPassword("");
    };

    return (

            Log into your account</h1>

                Email Address</label>
                 setEmail(e.target.value)}
                />
                Password</label>
                 setPassword(e.target.value)}
                />
                SIGN IN</button>

                    Don't have an account? Create one</Link>
                </p>
            </form>
        </main>
    );
};
export default Login;
```

### The Register page

Update the `Register.js` file to display the registration form that allows users to create an account using their email, username, and password.

```jsx
import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";

const Register = () => {
    const [username, setUsername] = useState("");
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log({ username, email, password });
        setEmail("");
        setUsername("");
        setPassword("");
    };
    return (

            Create an account</h1>

                Username</label>
                 setUsername(e.target.value)}
                />
                Email Address</label>
                 setEmail(e.target.value)}
                />
                Password</label>
                 setPassword(e.target.value)}
                />
                REGISTER</button>

                    Have an account? Sign in</Link>
                </p>
            </form>
        </main>
    );
};

export default Register;
```

### The Nav component

Update the `Nav.js` file to render a navigation bar that shows the application’s title and a sign-out button. Later in this article, I’ll guide you through adding Novu’s notification bell within this component.

```jsx
import React from "react";

const Nav = () => {
    const signOut = () => {
        alert("User signed out!");
    };
    return (

            Threadify</h2>

                Sign out</button>
            </div>
        </nav>
    );
};

export default Nav;
```

### The Home page

It is the home page after the user’s authentication. They can create and react to posts (threads) within the Home component. Copy the code below into the `Home.js` file.

```jsx
import React, { useState } from "react";
import Nav from "./Nav";

const Home = () => {
    const [thread, setThread] = useState("");

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log({ thread });
        setThread("");
    };
    return (
        <>

                Create a Thread</h2>

                        Title / Description</label>
                         setThread(e.target.value)}
                        />
                    </div>
                    CREATE THREAD</button>
                </form>
            </main>
        </>
    );
};

export default Home;
```

The code snippet above displays the navigation bar and an input field for the posts. We’ll update the component in the upcoming sections.

### The Replies page

This page is a dynamic route that allows users to reply and view comments on a post thread. Update the `Replies.js` file with the code snippet below:

```jsx
import React, { useState } from "react";

const Replies = () => {
    const [reply, setReply] = useState("");

    const handleSubmitReply = (e) => {
        e.preventDefault();
        console.log({ reply });
        setReply("");
    };

    return (

                Reply to the thread</label>
                 setReply(e.target.value)}
                    type='text'
                    name='reply'
                    className='modalInput'
                />

                SEND</button>
            </form>
        </main>
    );
};

export default Replies;
```

Congratulations! You’ve designed the application’s user interface. Next, you’ll learn how to register and log users into the application.

## User authentication with React and Node.js

Here, I’ll guide you through authenticating users and how to allow only authorised users to access protected pages within the web application.

> PS: In a real-world application, passwords are hashed and saved in a secure database. For simplicity purposes, I’ll store all the credentials in an array in this tutorial.

### Creating new users

Add a POST route on the server that accepts the user’s credentials – email, username, and password.

```javascript
//\ud83d\udc47\ud83c\udffb holds all the existing users
const users = [];
//\ud83d\udc47\ud83c\udffb generates a random string as ID
const generateID = () => Math.random().toString(36).substring(2, 10);

app.post("/api/register", async (req, res) => {
    const { email, password, username } = req.body;
    //\ud83d\udc47\ud83c\udffb holds the ID
    const id = generateID();
    //\ud83d\udc47\ud83c\udffb logs all the user's credentials to the console.
    console.log({ email, password, username, id });
});
```

Create a `signUp` function within the `Register.js` file that sends the user’s credentials to the endpoint on the server.

```javascript
const signUp = () => {
    fetch("http://localhost:4000/api/register", {
        method: "POST",
        body: JSON.stringify({
            email,
            password,
            username,
        }),
        headers: {
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            console.log(data);
        })
        .catch((err) => console.error(err));
};
```

Call the function when a user submits the form as done below:

```javascript
const handleSubmit = (e) => {
    e.preventDefault();
    //\ud83d\udc47\ud83c\udffb Triggers the function
    signUp();
    setEmail("");
    setUsername("");
    setPassword("");
};
```

Update the `/api/register` route to save the user’s credentials and return a response to the front-end.

```javascript
app.post("/api/register", async (req, res) => {
    const { email, password, username } = req.body;
    const id = generateID();
    //\ud83d\udc47\ud83c\udffb ensures there is no existing user with the same credentials
    const result = users.filter(
        (user) => user.email === email && user.password === password
    );
    //\ud83d\udc47\ud83c\udffb if true
    if (result.length === 0) {
        const newUser = { id, email, password, username };
        //\ud83d\udc47\ud83c\udffb adds the user to the database (array)
        users.push(newUser);
        //\ud83d\udc47\ud83c\udffb returns a success message
        return res.json({
            message: "Account created successfully!",
        });
    }
    //\ud83d\udc47\ud83c\udffb if there is an existing user
    res.json({
        error_message: "User already exists",
    });
});
```

The code snippet above accepts the user’s credentials from the React.js application and checks if there is no existing user with the same credentials before saving the user to the database (array).

Finally, display the server’s response by updating the `signUp` function as done below.

```javascript
//\ud83d\udc47\ud83c\udffb React Router's useNavigate hook
const navigate = useNavigate();

const signUp = () => {
    fetch("http://localhost:4000/api/register", {
        method: "POST",
        body: JSON.stringify({
            email,
            password,
            username,
        }),
        headers: {
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            if (data.error_message) {
                alert(data.error_message);
            } else {
                alert("Account created successfully!");
                navigate("/");
            }
        })
        .catch((err) => console.error(err));
};
```

The code snippet above displays a success or error message from the Node.js server and redirects the user to the login page after successfully creating the account.

### Logging users into the application

Add a POST route on the server that accepts the user’s email and password and authenticates the users before granting them access to the web application.

```javascript
app.post("/api/login", (req, res) => {
    const { email, password } = req.body;
    //\ud83d\udc47\ud83c\udffb checks if the user exists
    let result = users.filter(
        (user) => user.email === email && user.password === password
    );
    //\ud83d\udc47\ud83c\udffb if the user doesn't exist
    if (result.length !== 1) {
        return res.json({
            error_message: "Incorrect credentials",
        });
    }
    //\ud83d\udc47\ud83c\udffb Returns the id if successfuly logged in
    res.json({
        message: "Login successfully",
        id: result[0].id,
    });
});
```

Create a `loginUser` function within the `Login.js` file that sends the user’s email and password to the Node.js server.

```javascript
//\ud83d\udc47\ud83c\udffb React Router's useNavigate hook
const navigate = useNavigate();

const loginUser = () => {
    fetch("http://localhost:4000/api/login", {
        method: "POST",
        body: JSON.stringify({
            email,
            password,
        }),
        headers: {
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            if (data.error_message) {
                alert(data.error_message);
            } else {
                alert(data.message);
                navigate("/dashboard");
                localStorage.setItem("_id", data.id);
            }
        })
        .catch((err) => console.error(err));
};
```

The code snippet above sends the user’s credentials to the Node.js server and displays the response on the front end. The application redirects authenticated users to the `Home` component and saves their `id` to the local storage for easy identification.

Update the `signOut` function within the `Nav.js` file to remove the `id` from the local storage when a user logs out.

```javascript
const signOut = () => {
    localStorage.removeItem("_id");
    //\ud83d\udc47\ud83c\udffb redirects to the login page
    navigate("/");
};
```

## Creating and retrieving post threads within the application

Here, you’ll learn how to create and retrieve the posts from the Node.js server.

Add a POST route within the `index.js` file that accepts the post title and the user’s id from the React.js application.

```javascript
app.post("/api/create/thread", async (req, res) => {
    const { thread, userId } = req.body;
    const threadId = generateID();

    console.log({ thread, userId, threadId });
});
```

Next, send the user’s id and the post title to the server. Before then, let’s ensure the `Home.js` route is protected. Add a `useEffect` hook within the Home component to determine whether the user is authenticated.

```javascript
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

const Home = () => {
    const navigate = useNavigate();

    //\ud83d\udc47\ud83c\udffb The useEffect Hook
    useEffect(() => {
        const checkUser = () => {
            if (!localStorage.getItem("_id")) {
                navigate("/");
            } else {
                console.log("Authenticated");
            }
        };
        checkUser();
    }, [navigate]);

    return <>{/*--the UI elements*/}</>;
};
```

When users sign in to the application, we save their `id` to the local storage for easy identification. The code snippet above checks if the `id` exists; otherwise, the user is redirected to the login page.

Add a function within the Home component that sends the user’s id and the post title to the Node.js server when the form is submitted.

```javascript
const createThread = () => {
    fetch("http://localhost:4000/api/create/thread", {
        method: "POST",
        body: JSON.stringify({
            thread,
            userId: localStorage.getItem("_id"),
        }),
        headers: {
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            console.log(data);
        })
        .catch((err) => console.error(err));
};

//\ud83d\udc47\ud83c\udffb Triggered when the form is submitted
const handleSubmit = (e) => {
    e.preventDefault();
    //\ud83d\udc47\ud83c\udffb Calls the function
    createThread();
    setThread("");
};
```

Save the post and send all the available posts to the client for display.

```javascript
//\ud83d\udc47\ud83c\udffb holds all the posts created
const threadList = [];

app.post("/api/create/thread", async (req, res) => {
const { thread, userId } = req.body;
const threadId = generateID();

    //\ud83d\udc47\ud83c\udffb add post details to the array
    threadList.unshift({
        id: threadId,
        title: thread,
        userId,
        replies: [],
        likes: [],
    });

    //\ud83d\udc47\ud83c\udffb Returns a response containing the posts
    res.json({
        message: "Thread created successfully!",
        threads: threadList,
    });
});
```

The code snippet above accepts the user’s id and post title from the front end. Then, save an object that holds the post details and returns a response containing all the saved posts.

## Displaying the post threads

Create a state that will hold all the posts within the Home component.

```javascript
const [threadList, setThreadList] = useState([]);
```

Update the `createThread` function as done below:

```javascript
const createThread = () => {
    fetch("http://localhost:4000/api/create/thread", {
        method: "POST",
        body: JSON.stringify({
            thread,
            userId: localStorage.getItem("_id"),
        }),
        headers: {
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            alert(data.message);
            setThreadList(data.threads);
        })
        .catch((err) => console.error(err));
};
```

The `createThread` function retrieves all the posts available within the application and saves them into the `threadList` state.

Update the `Home.js` file to display the available posts as done below:

```jsx
return (
    <>

            Create a Thread</h2>

                {/*--form UI elements--*/}
            </form>

                {threadList.map((thread) => (

                        {thread.title}</p>

                        </div>
                    </div>
                ))}
            </div>
        </main>
    </>
);
```

- From the code snippet above:

  - All the posts are displayed within the Home component.

  - I added two new components – Likes and Comments. They both contain SVG icons from [Heroicons](<https://heroicons.com/>).

  - The `Likes` component accepts the post id and the number of likes on a post – length of the likes array on each post.

  - The `Comments` component accepts the length of the `replies` array, the post id, and its title.

Create a `utils` folder containing both components.

```bash
cd client/src
mkdir utils
touch Likes.js Comments.js
```

Copy the code below into the `Likes.js` file.

```jsx
import React from "react";

const Likes = ({ numberOfLikes, threadId }) => {
    return (

            </svg>

                {numberOfLikes === 0 ? "" : numberOfLikes}
            </p>
        </div>
    );
};

export default Likes;
```

The code snippet above contains the SVG element for displaying the Like icon. The component also renders the number of likes on a post.

Copy the code below into the `Comments.js` file.

```jsx
import React from "react";
import { useNavigate } from "react-router-dom";

const Comments = ({ numberOfComments, threadId }) => {
    const navigate = useNavigate();

    const handleAddComment = () => {
        navigate(`/${threadId}/replies`);
    };

    return (

            </svg>

                {numberOfComments === 0 ? "" : numberOfComments}
            </p>
        </div>
    );
};

export default Comments;
```

The code snippet contains the SVG element for the Comments button and the number of comments on the post.The `handleAddComment` function is triggered when a user clicks the comment icon. It redirects the user to the `Replies` component where they can view and add to the replies on each post.

So far, we’ve been able to display the available posts only when we create a new post. Next, let’s retrieve them when the component is mounted.

Add a GET route on the server that returns all the posts.

```javascript
app.get("/api/all/threads", (req, res) => {
    res.json({
        threads: threadList,
    });
});
```

Update the `useEffect` hook within the Home component to fetch all the posts from the server.

```javascript
useEffect(() => {
    const checkUser = () => {
        if (!localStorage.getItem("_id")) {
            navigate("/");
        } else {
            fetch("http://localhost:4000/api/all/threads")
                .then((res) => res.json())
                .then((data) => setThreadList(data.threads))
                .catch((err) => console.error(err));
        }
    };
    checkUser();
}, [navigate]);
```

Congratulations on making it this far! Next, you’ll learn how to react and reply to the posts.

## How to react and reply to each posts

In this section, you’ll learn how to react and reply to each post. Users will be able to drop a like or comment on each post.

### Reacting to each post

Create a function within the `Likes.js` file that runs when a user clicks on the Like icon.

```javascript
const Likes = ({ numberOfLikes, threadId }) => {
    const handleLikeFunction = () => {
        alert("You just liked the post!");
    };

    return (

                {/*--other UI elements*/}
            </svg>
        </div>
    );
};
```

Create a POST route on the server that validates the reaction.

```javascript
app.post("/api/thread/like", (req, res) => {
    //\ud83d\udc47\ud83c\udffb accepts the post id and the user id
    const { threadId, userId } = req.body;
    //\ud83d\udc47\ud83c\udffb gets the reacted post
    const result = threadList.filter((thread) => thread.id === threadId);
    //\ud83d\udc47\ud83c\udffb gets the likes property
    const threadLikes = result[0].likes;
    //\ud83d\udc47\ud83c\udffb authenticates the reaction
    const authenticateReaction = threadLikes.filter((user) => user === userId);
    //\ud83d\udc47\ud83c\udffb adds the users to the likes array
    if (authenticateReaction.length === 0) {
        threadLikes.push(userId);
        return res.json({
            message: "You've reacted to the post!",
        });
    }
    //\ud83d\udc47\ud83c\udffb Returns an error user has reacted to the post earlier
    res.json({
        error_message: "You can only react once!",
    });
});
```

- From the code snippet above:

  - The route accepts the post id and the user’s id from the React.js application and searches for the post that received a reaction.

  - Then, validates the reaction before adding the user to the likes array.

Update the `handleLikeFunction` to send a POST request to the `api/thread/like` endpoint whenever a user reacts to the post.

```javascript
const handleLikeFunction = () => {
    fetch("http://localhost:4000/api/thread/like", {
        method: "POST",
        body: JSON.stringify({
            threadId,
            userId: localStorage.getItem("_id"),
        }),
        headers: {
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            if (data.error_message) {
                alert(data.error_message);
            } else {
                alert(data.message);
            }
        })
        .catch((err) => console.error(err));
};
```

### Displaying the replies on each post

Here, I’ll guide you through displaying the replies on each post within the React app.

Add a POST route on the server that accepts a post ID from the front end and returns the title and all the responses under such post.

```javascript
app.post("/api/thread/replies", (req, res) => {
    //\ud83d\udc47\ud83c\udffb The post ID
    const { id } = req.body;
    //\ud83d\udc47\ud83c\udffb searches for the post
    const result = threadList.filter((thread) => thread.id === id);
    //\ud83d\udc47\ud83c\udffb return the title and replies
    res.json({
        replies: result[0].replies,
        title: result[0].title,
    });
});
```

Next, update the Replies component to send a request to the `api/thread/replies` endpoint on the server and display the title and replies when the page loads.

```javascript
import React, { useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";

const Replies = () => {
    const [replyList, setReplyList] = useState([]);
    const [reply, setReply] = useState("");
    const [title, setTitle] = useState("");
    const navigate = useNavigate();
    const { id } = useParams();

    useEffect(() => {
        const fetchReplies = () => {
            fetch("http://localhost:4000/api/thread/replies", {
                method: "POST",
                body: JSON.stringify({
                    id,
                }),
                headers: {
                    "Content-Type": "application/json",
                },
            })
                .then((res) => res.json())
                .then((data) => {
                    setReplyList(data.replies);
                    setTitle(data.title);
                })
                .catch((err) => console.error(err));
        };
        fetchReplies();
    }, [id]);

    //\ud83d\udc47\ud83c\udffb This function when triggered when we add a new reply
    const handleSubmitReply = (e) => {
        e.preventDefault();
        console.log({ reply });
        setReply("");
    };
    return {/*--UI elements--*/}</main>;
};
```

- From the code snippet above:

  - The `useEffect` hook sends a request to the server’s endpoint and retrieves the post title and its replies.

  - The `title` and `replyList` states hold both the title and replies respectively.

  - The variable `id` holds the post ID retrieved from the URL via dynamic routing in React Router.

Display the replies on each post as done below:

```jsx
return (

        {title}</h1>

            Reply to the thread</label>
             setReply(e.target.value)}
                type='text'
                name='reply'
                className='modalInput'
            />

            SEND</button>
        </form>

            {replyList.map((reply) => (

                    {reply.text}</p>

                        by {reply.name}</p>
                    </div>
                </div>
            ))}
        </div>
    </main>
);
```

The code snippet above shows the layout of the Replies component displaying the post title, replies, and a form field for replying to a post.

### Creating the post reply functionality

Create an endpoint on the server that allows users to add new replies as done below:

```javascript
app.post("/api/create/reply", async (req, res) => {
    //\ud83d\udc47\ud83c\udffb accepts the post id, user id, and reply
    const { id, userId, reply } = req.body;
    //\ud83d\udc47\ud83c\udffb search for the exact post that was replied to
    const result = threadList.filter((thread) => thread.id === id);
    //\ud83d\udc47\ud83c\udffb search for the user via its id
    const user = users.filter((user) => user.id === userId);
    //\ud83d\udc47\ud83c\udffb saves the user name and reply
    result[0].replies.unshift({
        userId: user[0].id,
        name: user[0].username,
        text: reply,
    });

    res.json({
        message: "Response added successfully!",
    });
});
```

The code snippet above accepts the post id, user id, and reply from the React app, searches for the post via its ID, and adds the user’s id, username, and reply to the post replies.

Create a function that sends a request to the `/api/create/reply` endpoint.

```javascript
const addReply = () => {
    fetch("http://localhost:4000/api/create/reply", {
        method: "POST",
        body: JSON.stringify({
            id,
            userId: localStorage.getItem("_id"),
            reply,
        }),
        headers: {
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            alert(data.message);
            navigate("/dashboard");
        })
        .catch((err) => console.error(err));
};

const handleSubmitReply = (e) => {
    e.preventDefault();
    //\ud83d\udc47\ud83c\udffb calls the function
    addReply();
    setReply("");
};
```

Congratulations on making it thus far! In the upcoming sections, you’ll learn how to send notifications to multiple users using the Topics API provided by Novu.

## How to send notifications via the Topic API in Novu

In this section, we’ll use the [Novu Topic API](<https://docs.novu.co/platform/topics/>) to send notifications to multiple users simultaneously. To do this, the API allows us to create a unique topic, assign subscribers to the topic, and send a bulk notification to the subscribers at once.

### Setting up your Novu admin panel

Navigate into the client folder and create a Novu project by running the code below.

```bash
cd client
npx novu init
```

Select your application name and sign in to Novu. The code snippet below contains the steps you should follow after running `npx novu init`.

```bash
Now let's setup your account and send your first notification
? What is your application name? Forum App
? Now lets setup your environment. How would you like to proceed? Create a free cloud account (Recommended)
? Create your account with: Sign-in with GitHub
? I accept the Terms and Conditions (https://novu.co/terms) and have read the Privacy Policy (https://novu.co/privacy) Yes
\u2714 Created your account successfully.

  We've created a demo web page for you to see novu notifications in action.
  Visit: http://localhost:51422/demo to continue
```

Visit the demo page, copy your subscriber ID from the page, and click the Skip Tutorial button.

Create a notification template with a workflow as shown below:

Update the In-App notification template to send this message to the post creator when there is a new reply.

```bash
Someone just dropped a reply to the thread!
```

Install the Novu Notification package within your React project.

```bash
npm install @novu/notification-center
```

Update the `components/Nav.js` file to contain the Novu notification bell according to its [documentation](<https://docs.novu.co/notification-center/getting-started#react-component>).

```jsx
import React from "react";
import {
    NovuProvider,
    PopoverNotificationCenter,
    NotificationBell,
} from "@novu/notification-center";
import { useNavigate } from "react-router-dom";

const Nav = () => {
    const navigate = useNavigate();

    const onNotificationClick = (notification) =>
        navigate(notification.cta.data.url);

    const signOut = () => {
        localStorage.removeItem("_id");
        navigate("/");
    };
    return (

            Threadify</h2>

                        {({ unseenCount }) => (

                        )}
                    </PopoverNotificationCenter>
                </NovuProvider>

                Sign out</button>
            </div>
        </nav>
    );
};
```

The code snippet above adds Novu’s notification bell icon to the Nav component, enabling us to view all the notifications in our app. Replace the Subscriber ID variable with yours.

Select Settings on your Novu Admin Panel and copy your App ID and API key. Ensure you add the App ID into its variable within the `Nav.js` file.

### Configuring Novu on the server

Install the Novu SDK for Node.js into the server folder.

```bash
npm install @novu/node
```

Import Novu from the package and create an instance using your API Key. Replace the API Key variable with your API key copied earlier.

```javascript
const { Novu } = require("@novu/node");
const novu = new Novu("");
```

### Sending notifications via the Novu Topic API

To begin with, you’ll need to add the users as subscribers when they login to the application. Therefore, update the `/api/register` route to add the user as a Novu subscriber.

```javascript
app.post("/api/register", async (req, res) => {
    const { email, password, username } = req.body;
    const id = generateID();
    const result = users.filter(
        (user) => user.email === email && user.password === password
    );

    if (result.length === 0) {
        const newUser = { id, email, password, username };
        //\ud83d\udc47\ud83c\udffb add the user as a subscriber
        await novu.subscribers.identify(id, { email: email });

        users.push(newUser);
        return res.json({
            message: "Account created successfully!",
        });
    }
    res.json({
        error_message: "User already exists",
    });
});
```

The code snippet above creates a Novu subscriber via the user’s email and id.

Next, add each new post as a Novu topic and add the user as a subscriber to the topic.

```javascript
app.post("/api/create/thread", async (req, res) => {
    const { thread, userId } = req.body;
    let threadId = generateID();
    threadList.unshift({
        id: threadId,
        title: thread,
        userId,
        replies: [],
        likes: [],
    });
//\ud83d\udc47\ud83c\udffb creates a new topic from the post
    await novu.topics.create({
        key: threadId,
        name: thread,
    });
//\ud83d\udc47\ud83c\udffb add the user as a subscriber
    await novu.topics.addSubscribers(threadId, {
        subscribers: [userId],
        //replace with your subscriber ID to test run
        // subscribers: [""],
    });

    res.json({
        message: "Thread created successfully!",
        threads: threadList,
    });
});
```

Finally, send notifications to the subscriber when there is a new response on the thread

```javascript
app.post("/api/create/reply", async (req, res) => {
    const { id, userId, reply } = req.body;
    const result = threadList.filter((thread) => thread.id === id);
    const user = users.filter((user) => user.id === userId);
    result[0].replies.unshift({ name: user[0].username, text: reply });
//\ud83d\udc47\ud83c\udffb Triggers the function when there is a new reply
    await novu.trigger("topicnotification", {
        to: [{ type: "Topic", topicKey: id }],
    });

    res.json({
        message: "Response added successfully!",
    });
});
```

Congratulations!🎉 You’ve completed the project for this tutorial.

## Conclusion

So far, you’ve learnt how to authenticate users, communicate between a React and Node.js app, and send bulk notifications using the Novu Topic API.

Novu is an open-source notification infrastructure that enables you to send SMS, chat, push, and e-mail notifications from a single dashboard. The Topic API is just one of the exciting features Novu provides; feel free to learn more about us [here](<https://docs.novu.co/overview/introduction>).

The source code for this tutorial is available here:

[https://github.com/novuhq/blog/tree/main/forum-system-with-react-novu-node](<https://github.com/novuhq/blog/tree/main/forum-system-with-react-novu-node>)

Thank you for reading!
