🔥 2 TRICKS 🔥 to build a Meetup.com clone with React in 30 minutes 🪄✨
Tags: webdev, javascript, react, programming
Published: 2023-08-14 12:15:07

In this tutorial, you'll learn how to build a Meetup.com clone we will touch:
- Create and join online events
- Add comments under an upcoming event
- **[TRICK 1]**: Building an authentication / authorization in 5 minutes with SuperTokens
- **[TRICK 2]**: Notify when someone joins and comment on their event in 10 minutes with Novu.

So Let's meet up!

![Meetup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/awkw74kfuwh1ggx5kwbm.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 - **Websockets**), Emails, SMSs and so on.

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

<hr />

## Let's set it up! 🔥

Here, I'll walk you through creating the project setup for the application. We'll use React.js for the front end and Node.js for the backend server.

Create a folder for the web application as done below.

```bash
mkdir meetup-clone
cd meetup-clone
mkdir client server
```

### Adding a 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 ExpressJS. 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 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.

```jsx
//👇🏻 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 with [Vite](https://vitejs.dev/).

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

Install [React Icons](https://react-icons.github.io/react-icons) and [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 react-icons
```

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 done below.

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

Copy the [CSS file required for styling the project](https://github.com/novuhq/blog/blob/main/meetup-clone-react-supertokens-nodejs/client/src/index.css) into the `src/index.css` file.

<hr />

## Crafting the user interface 🧪

Here, we'll create the user interface for the Meetup clone to enable users to create and join events and view event details.

Create a pages folder within the `client/src` folder containing the `CreateEvent.jsx`, `Dashboard.jsx`, `EventDetails.jsx`, `Events.jsx`, `EventsCategories.jsx`, and `Home.jsx`.

```bash
cd client/src
mkdir pages
cd pages
touch Home.jsx Dashboard.jsx CreateEvent.jsx EventDetails.jsx Events.jsx EventsCategories.jsx
```

- From the code snippet above,
    - The `Home.jsx` component is the application's homepage where users can view all upcoming events.
    - The `Dashboard.jsx` component displays all the user's events.
    - The `CreateEvent.jsx` component enables users to provide details about an event and create a new event.
    - The `EventDetails.jsx` component provides all the details about an event.
    - The `Events.jsx` and `EventsCategories.jsx` components are similar. The `Events.jsx` component displays all the available events, and the `EventsCategories.jsx` displays all the events under a particular category.

Next, create a components folder within the `client/src` folder containing the `CategoriesSection.jsx`, `EventsSection.jsx`, `Footer.jsx`, `Hero.jsx`, and `Nav.jsx` components. These components are the different sections of the homepage.

```bash
cd client/src
mkdir components
cd components
touch CategoriesSection.jsx EventsSection.jsx Footer.jsx Hero.jsx Nav.jsx
```

Update the `App.jsx` file to render the pages using React Router.

```jsx
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Dashboard from "./pages/Dashboard";
import Events from "./pages/Events";
import EventsCategory from "./pages/EventsCategory";
import CreateEvent from "./pages/CreateEvent";
import EventDetails from "./pages/EventDetails";

function App() {
	return (
		<Router>
			<Routes>
				<Route path='/' element={<Home />} />
				<Route path='/dashboard' element={<Dashboard />} />
				<Route path='/events/all' element={<Events />} />
				<Route path='/events/:category' element={<EventsCategory />} />
				<Route path='/create/event' element={<CreateEvent />} />
				<Route path='/event/:slug' element={<EventDetails />} />
			</Routes>
		</Router>
	);
}

export default App;
```

### Home Page 🏠

The Home page displays all the available events within the application and enables users to navigate to other pages on the website. Copy the code below into the `Home.jsx` file.

```jsx
import CategoriesSection from "../components/CategoriesSection";
import EventsSection from "../components/EventsSection";
import Footer from "../components/Footer";
import Hero from "../components/Hero";
import Nav from "../components/Nav";

const Home = () => {
	return (
		<div>
			<Nav />
			<Hero />
			<EventsSection />
			<CategoriesSection />
			<Footer />
		</div>
	);
};

export default Home;
```

![Home Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ybk0vwm2adf43spu5ndw.gif)


### Adding Events 📕

These pages display all the events within the application or within a particular category.

```jsx
import event from "../assets/event.jpeg";
import Nav from "../components/Nav";
import { AiOutlineCalendar } from "react-icons/ai";
import { BsCheckCircle } from "react-icons/bs";
import { ImLocation2 } from "react-icons/im";
import { Link } from "react-router-dom";

const Events = () => {
	return (
		<>
			<Nav />
			<div className='home_events' style={{ paddingTop: "20px" }}>
				<h1 style={{ fontSize: "30px", marginBottom: "20px" }}>All Events</h1>

				<div className='body_events'>
					<Link to={`/event/slug`} className='i_event'>
						<img src={event} alt='Event' className='i_image' />
						<div className='i_content'>
							<h2 style={{ marginBottom: "10px" }}>Novu Community Call</h2>
							<p style={{ marginBottom: "10px", opacity: 0.7 }}>
								Hosted by: Novu Development Team
							</p>
							<div
								style={{
									display: "flex",
									alignItems: "center",
									opacity: 0.7,
									marginBottom: "10px",
								}}
							>
								<AiOutlineCalendar style={{ marginRight: "5px" }} />
								<p>Starting at 8:00pm</p>
							</div>
							<div
								style={{
									display: "flex",
									alignItems: "center",
									opacity: 0.7,
									marginBottom: "10px",
								}}
							>
								<ImLocation2 style={{ marginRight: "5px", color: "red" }} />
								<p>Online (Discord Channel)</p>
							</div>
							<div
								style={{
									display: "flex",
									alignItems: "center",
									opacity: 0.7,
									marginBottom: "10px",
								}}
							>
								<BsCheckCircle style={{ marginRight: "5px", color: "green" }} />
								<p>12 going</p>
							</div>
						</div>
					</Link>
				</div>
			</div>
		</>
	);
};

export default Events;
```


![The Create Event page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m7jwu8bjkfaex6zf1i3h.png)


### The Create Event page 📅

This page displays a form field that accepts the details of the new event. Copy the code snippet below into the `CreateEvent.jsx` file.

```jsx
import Nav from "../components/Nav";
import { useState } from "react";
import { postNewEvent } from "../utils/util";
import { useNavigate } from "react-router-dom";

const CreateEvent = () => {
	const [title, setTitle] = useState("");
	const navigate = useNavigate();
	const [location, setLocation] = useState("");
	const [category, setCategory] = useState("");
	const [description, setDescription] = useState("");
	const [startTime, setStartTime] = useState("");

	const handleSubmit = (e) => {
		e.preventDefault();
		console.log({ title, location, category, startTime, description });
		setTitle("");
		setLocation("");
		setCategory("");
		setDescription("");
		setStartTime("");
	};
	return (
		<div className='create_event'>
			<Nav />
			<div style={{ padding: "30px" }}>
				<h2
					style={{
						textAlign: "center",
						marginBottom: "30px",
						color: "#1d5d9b",
					}}
				>
					Create new event
				</h2>
				<form className='create_form' onSubmit={handleSubmit}>
					<label htmlFor='title'>Title</label>
					<input
						type='text'
						name='title'
						id='title'
						value={title}
						onChange={(e) => setTitle(e.target.value)}
						required
						className='event_title'
					/>

					<label htmlFor='location'>Location</label>
					<input
						type='text'
						name='location'
						id='location'
						value={location}
						onChange={(e) => setLocation(e.target.value)}
						className='event_title'
						required
					/>

					<div
						style={{
							width: "100%",
							display: "flex",
							alignItems: "center",
							justifyContent: "space-between",
						}}
					>
						<div
							style={{
								display: "flex",
								flexDirection: "column",
								width: "50%",
								marginRight: "7px",
							}}
						>
							<label htmlFor='startTime'>Starting Time</label>
							<input
								type='time'
								name='startTime'
								id='startTime'
								value={startTime}
								onChange={(e) => setStartTime(e.target.value)}
								className='event_title'
								required
							/>
						</div>
						<div
							style={{ display: "flex", flexDirection: "column", width: "50%" }}
						>
							<label htmlFor='category'>Category</label>
							<select
								value={category}
								onChange={(e) => setCategory(e.target.value)}
								className='event_title'
								required
							>
								<option value='travel-and-outdoor'>Travel and Outdoor</option>
								<option value='religion'>Religion</option>
								<option value='sports-and-fitness'>Sports and Fitness</option>
								<option value='social-activities'>Social Activities</option>
							</select>
						</div>
					</div>

					<label htmlFor='description'>Description</label>
					<textarea
						rows={8}
						value={description}
						onChange={(e) => setDescription(e.target.value)}
						required
					/>
					<button className='createEventBtn' type='submit'>
						Create Event
					</button>
				</form>
			</div>
		</div>
	);
};

export default CreateEvent;
```

![The Dashboard page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vtnwasu0fwahws6m8fg8.png)

### The Dashboard page

The Dashboard page shows the user's events and allows the user to create events.

```jsx
import Nav from "../components/Nav";
import { Link } from "react-router-dom";

const Dashboard = () => {
	return (
		<div className='dashboard_container'>
			<Nav />
			<div className='dashboard_main'>
				<section className='header_events'>
					<h1 style={{ fontSize: "30px" }}>Your Events</h1>
					<Link to='/create/event' className='link'>
						Create new event
					</Link>
				</section>
				<div>{/*--user's events*/}</div>
			</div>
		</div>
	);
};

export default Dashboard;
```


![The Event Details page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1q5gau2ar2iyjgidl6ro.png)

### The Event Details page 🗓️

This page displays information about an event, enables users to register for an event with just a click, and leave a comment about an event.

```jsx
import Nav from "../components/Nav";
import event from "../assets/event.jpeg";
import { useState } from "react";
import { useParams } from "react-router-dom";

const EventDetails = () => {
	const [comment, setComment] = useState("");
	const { slug } = useParams();

	const addComment = (e) => {
		e.preventDefault();
		console.log(comment, slug);
	};
	return (
		<div>
			<Nav />
			<header className='details_header'>
				<h2 style={{ marginBottom: "15px" }}>Title</h2>
				<p style={{ opacity: 0.6 }}>
					Hosted by: <span style={{ fontWeight: "bold" }}>Host</span>
				</p>
			</header>
			<main className='details_main'>
				<div className='details_content'>
					<img src={event} alt='Event' className='details_image' />
					<div style={{ marginBottom: "30px" }}>Description</div>
					<div style={{ padding: "30px 0" }}>
						<h2 style={{ color: "#1d5d9b", marginBottom: "15px" }}>
							Attendees
						</h2>
						<p style={{ opacity: 0.6 }}>Attendees</p>
					</div>

					<div className='comments'>
						<h2 style={{ color: "#1d5d9b" }}>Comments</h2>
						<form className='comment_form' onSubmit={addComment}>
							<textarea
								rows={4}
								className='commentInput'
								value={comment}
								onChange={(e) => setComment(e.target.value)}
								required
							/>
							<button className='buttons commentBtn'>Comment</button>
						</form>

						<div className='comment_section'>
							<div
								style={{
									padding: "15px",
									border: "1px solid #ddd",
									borderRadius: "3px",
									marginBottom: "10px",
								}}
								key={comment.id}
							>
								<p style={{ color: "#1d5d9b", marginBottom: "3px" }}>@User</p>
								<p style={{ opacity: 0.5 }}>Comment</p>
							</div>
						</div>
					</div>
				</div>
				<div className='details_cta'>
					<p style={{ marginBottom: "10px", opacity: "0.6" }}>
						Click here to register
					</p>
					<button className='buttons registerBtn'>Register</button>
				</div>
			</main>
		</div>
	);
};

export default EventDetails;
```


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

<hr />

## Authentication and Authorization 🔑

[SuperTokens](https://github.com/supertokens/supertokens-core) is an open-source authentication service provider that enables you to add secure and seamless user authentication and session management to your software applications.

It also provides a prebuilt UI for various forms of authentication, such as email and password login, social login, and passwordless login. With [SuperTokens](https://supertokens.com/), you can add authentication to both web and mobile applications in a few minutes.

## Adding it to our app 🎉

Here, you'll learn how to add authentication to your React and Node.js applications with SuperTokens. You can add SuperTokens automatically to your application or manually to an existing project.

To install SuperTokens automatically, run the code snippet below to install a starter app.

```bash
npx create-supertokens-app@latest --recipe=emailpassword
```

> 💡 PS: The recipe flag represents the authentication method you want to set up with SuperTokens. You check the [documentation for a complete guide.](https://supertokens.com/docs/guides)


### Configuring SuperTokens

Go to the [homepage](https://supertokens.com/) and create an account.

Head to your dashboard and fill the Get Started form accordingly.


![Configuring SuperTokens](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fz1j4391i1cu3nq5ikx9.png)


Next, switch to the Core Configuration Details menu tab to generate your `Core connectionURI` and `Core API key.`


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


### Connect SuperTokens with React app 📳

Install SuperTokens to the React app by running the code snippet below.

```bash
npm i -s supertokens-auth-react
```

Add the code snippet below to the `App.jsx` file.

```jsx
import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react";
import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui";
import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui";
import * as reactRouterDom from "react-router-dom";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
import { SessionAuth } from "supertokens-auth-react/recipe/session";

SuperTokens.init({
	appInfo: {
		appName: "meetup-clone",
		apiDomain: "http://localhost:4000",
		websiteDomain: "http://localhost:5173",
		apiBasePath: "/auth",
		websiteBasePath: "/",
	},
	recipeList: [EmailPassword.init(), Session.init()],
});
```

The code snippet above initialises SuperTokens with Email & Password within the React app.

Finally, update the routes as done below.

```jsx
return (
	<SuperTokensWrapper>
		<Router>
			<Routes>
				{getSuperTokensRoutesForReactRouterDom(reactRouterDom, [
					EmailPasswordPreBuiltUI,
				])}
				<Route
					path='/'
					element={
						<SessionAuth>
							<Home />
						</SessionAuth>
					}
				/>
				<Route path='/' element={<Home />} />
				<Route
					path='/dashboard'
					element={
						<SessionAuth>
							<Dashboard />
						</SessionAuth>
					}
				/>
				<Route
					path='/events/all'
					element={
						<SessionAuth>
							<Events />
						</SessionAuth>
					}
				/>
				<Route
					path='/events/:category'
					element={
						<SessionAuth>
							<EventsCategory />
						</SessionAuth>
					}
				/>
				<Route
					path='/create/event'
					element={
						<SessionAuth>
							<CreateEvent />
						</SessionAuth>
					}
				/>
				<Route
					path='/event/:slug'
					element={
						<SessionAuth>
							<EventDetails />
						</SessionAuth>
					}
				/>
			</Routes>
		</Router>
	</SuperTokensWrapper>
);
```

From the code snippet above, I wrapped all the routes with the `<SessionAuth/>` component provided by SuperTokens to protect them from unauthenticated users until they sign into the application. 


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

### Adding SuperTokens to the Node.js server

Install SuperTokens to the Node.js app by running the code below.

```bash
npm i -s supertokens-node
```

Add the code snippet below to initialise SuperTokens.

```jsx
const supertokens = require("supertokens-node");
const Session = require("supertokens-node/recipe/session");
const EmailPassword = require("supertokens-node/recipe/emailpassword");
const { middleware } = require("supertokens-node/framework/express");
const { errorHandler } = require("supertokens-node/framework/express");

supertokens.init({
	framework: "express",
	supertokens: {
		connectionURI: "<YOUR_CONNECTION_URL>",
		apiKey: "<YOUR_API_KEY>",
	},
	appInfo: {
		appName: "meetup-clone",
		apiDomain: "http://localhost:4000",
		websiteDomain: "http://localhost:5173",
		apiBasePath: "/auth",
		websiteBasePath: "/",
	},
	recipeList: [EmailPassword.init(), Session.init()],
});
```

Update the server CORS as shown below.

```jsx
app.use(
	cors({
		origin: "http://localhost:5173",
		allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()],
		credentials: true,
	})
);

// IMPORTANT: CORS should be before the below line.
app.use(middleware());
```

Finally, add the error handler provided by SuperTokens.

```jsx
// ...your API routes

// Add this AFTER all your routes
app.use(errorHandler());
```

Congratulations!🎉 You've successfully added SuperTokens to the React and Node.js application. If you encounter any issues, feel free to follow [the SuperTokens installation guide](https://supertokens.com/docs/guides).

## Communicating with the Node.js server

In this section, you'll learn how to communicate with the Node.js server by retrieving and creating events within the application.

Before we begin, create a `utils` folder containing a `util.js` file within the React app.

```bash
cd client
mkdir utils
cd utils
touch util.js
```

### Getting existing events

Create an events array within the `index.js` file on the server containing the event's attributes.

```jsx
const events = [
	{
		id: generateID(),
		title: "Novu Community Call",
		slug: "novu-community-call",
		host: "Novu Development Team",
		category: "social-activities",
		start_time: "8:00pm",
		location: "Online (Discord Channel)",
		comments: [
			{ user: "nevodavid", id: generateID(), comment: "Can't wait!😍" },
			{ user: "emil_pearce", id: generateID(), comment: "Let's go!🚀" },
		],
		attendees: [
			"nevodavid",
			"emil_pearce",
			"tomer_barnea",
			"unicodeveloper",
			"scopsy",
		],
		description:
			"Dear attendee,\n We hope this message finds you well! We're excited to invite you to our upcoming Novu Community Call, where we will come together to share insights, updates, and engage in meaningful discussions. Your presence and contributions are highly valued as we continue to grow and strengthen our vibrant Novu community.",
	},
	{
		id: generateID(),
		title: "Novu Team Hangout",
		slug: "novu-team-hangout",
		host: "Novu Team",
		category: "social-activities",
		start_time: "12:30pm",
		location: "Online (Google Meet)",
		comments: [
			{ user: "nevodavid", id: generateID(), comment: "Can't wait!😍" },
			{ user: "emil_pearce", id: generateID(), comment: "Let's go!🚀" },
		],
		attendees: ["nevodavid", "tomer_barnea", "unicodeveloper", "scopsy"],
		description:
			"Dear attendee,\n We hope this message finds you well! We're excited to invite you to our upcoming Novu Community Call, where we will come together to share insights, updates, and engage in meaningful discussions. Your presence and contributions are highly valued as we continue to grow and strengthen our vibrant Novu community.",
	},
];
```

Add another endpoint that returns the events in a JSON format.

```jsx
app.get("/events", (req, res) => {
	res.json({
		message: "Success!",
		events,
	});
});
```

Next, create a function within the `utils/util.js` file that sends a request to the endpoint from the React app.

```jsx
export const fetchEvents = (setEvents) => {
	fetch("http://localhost:4000/events")
		.then((res) => res.json())
		.then((data) => {
			if (data.message) {
				setEvents(data.events);
			}
		})
		.catch((err) => console.error(err));
};
```

Finally, execute the function when the Home component mounts.

```jsx
const Home = () => {
	const [events, setEvents] = useState([]);
	//generates a random string as ID
	const generateID = () => Math.random().toString(36).substring(2, 10);

	useEffect(() => {
		fetchEvents(setEvents);
		//save a user_id property to the database
		if (!localStorage.getItem("user_id")) {
			localStorage.setItem("user_id", generateID());
		}
	}, []);

	return <div>{/*--render events from the server--*/}</div>;
};
```

The code snippet above retrieves all the events from the server and creates a `user_id` property on the web browser to enable us to identify each user.

> *💡 PS: I'm using local storage because this is a small application. If you are using SuperTokens in a production environment, kindly check the [SuperTokens backend guide](https://supertokens.com/docs/emailpassword/pre-built-ui/setup/backend).*
> 

### Getting events by categories

To fetch the events under a particular category, the `/events/:category` client route accepts the category name as part of its path name and sends it to the server to return all the events under the category.

```jsx
<Route
	path='/events/:category'
	element={
		<SessionAuth>
			<EventsCategory />
		</SessionAuth>
	}
/>
```

Add an endpoint on the server that retrieves the events based on their category name.

```jsx
app.post("/event/category", (req, res) => {
	const { category } = req.body;
	const result = events.filter((e) => e.category === category);
	res.json({ message: "Success!", events: result });
});
```

Send a request to the endpoint on the server and display the events under a particular category.

```jsx
export const fetchEventByCategory = (category, setEvents) => {
	fetch("http://localhost:4000/event/category", {
		method: "POST",
		body: JSON.stringify({ category }),
		headers: {
			Accept: "application/json",
			"Content-Type": "application/json",
		},
	})
		.then((res) => res.json())
		.then((data) => {
			if (data.message) {
				console.log(data.events);
				setEvents(data.events);
			}
		})
		.catch((err) => console.error(err));
};
```


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

### Retrieving event details via slug

To do this, add a POST route to the server that filters the events via the slug received from the React app.

```jsx
app.post("/event/slug", (req, res) => {
	const { slug } = req.body;
	const result = events.filter((e) => e.slug === slug);
	res.json({ message: "Success!", event: result[0] });
});
```

The code snippet above uses the event's slug to retrieve its entire event object from the server.

Create a function within the `utils/util.js` that sends a request to the endpoint on the server.

```jsx
//👇🏻Within the util.js file
export const fetchEventBySlug = (slug, setEvent) => {
	fetch("http://localhost:4000/event/slug", {
		method: "POST",
		body: JSON.stringify({ slug }),
		headers: {
			Accept: "application/json",
			"Content-Type": "application/json",
		},
	})
		.then((res) => res.json())
		.then((data) => {
			if (data.message) {
				setEvent(data.event);
			}
		})
		.catch((err) => console.error(err));
};
```

Execute the function on component mount within the `EventDetails` component and display the properties accordingly.

```jsx
import { useNavigate, useParams } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom"
import { useState, useEffect } from "react"
import { fetchEventBySlug } from "../utils/util"

export const EventDetails = () => {
	const [eventDetails, setEventDetails] = useState({});
	const { slug } = useParams();

	useEffect(() => {
		fetchEventBySlug(slug, setEventDetails);
		setLoading(false);
	}, [slug]);

	return <div>{/*--displays event details --*/}</div>;
};
```


![Creating new events](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0u0zjowizqyh94cizn7z.gif)



### Creating new events

Create an endpoint that adds a new event to the `events` array.

```jsx
//👇🏻 generates slug from a text
const createSlug = (text) => {
	let slug = text
		.trim()
		.toLowerCase()
		.replace(/[^\w\s-]/g, "");
	slug = slug.replace(/\s+/g, "-");
	return slug;
};
//👇🏻 generates a random string as ID
const generateID = () => Math.random().toString(36).substring(2, 10);

//👇🏻 endpoint for creating new events
app.post("/create/event", async (req, res) => {
	const { title, location, startTime, category, description, host } = req.body;

	const eventObject = {
		id: generateID(),
		title,
		slug: createSlug(title),
		host,
		category,
		start_time: startTime,
		location,
		comments: [],
		attendees: [],
		description,
	};
	events.unshift(eventObject);
	res.json({ message: "Event added successfully!✅" });
});
```

The code snippet above accepts the event attributes from the form within the `CreateEvent` component and adds the event to the `events` array when the user submits the form.


![CreateEvent](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6kk4pm3ro63y8r6xv42r.png)

Execute the function below when the user submits the form.

```jsx
//👇🏻 runs when a user submits the form
const handleSubmit = (e) => {
	e.preventDefault();
	postNewEvent(
		title,
		location,
		category,
		startTime,
		description,
		localStorage.getItem("user_id")
	);
};
//👇🏻 makes a request to the server
const postNewEvent = () => {
	fetch("http://localhost:4000/create/event", {
		method: "POST",
		body: JSON.stringify({
			title,
			location,
			category,
			startTime,
			description,
			host,
		}),
		headers: {
			Accept: "application/json",
			"Content-Type": "application/json",
		},
	})
		.then((res) => res.json())
		.then((data) => {
			if (data.message) {
				alert(data.message);
				navigate("/dashboard");
			}
		})
		.catch((err) => console.error(err));
};
```

### Adding comments to events

To add comments to an event, create another endpoint that accepts the user's id, the event's slug, and the comment from the React app.

```jsx
app.post("/event/comment", async (req, res) => {
	const { comment, user, slug } = req.body;
	for (let i = 0; i < events.length; i++) {
		if (events[i].slug === slug) {
			events[i].comments.unshift({
				user,
				id: generateID(),
				comment,
			});

			return res.json({ message: "Comment added successfully!✅" });
		}
	}
```

The code snippet above retrieves the event with the same slug from the request and updates the `comment` property with the latest comment.


![Add comment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8td6bqxlgtf92c3mkcba.gif)

Execute the function when a user drops a new comment.

```jsx
const addComment = (e) => {
	e.preventDefault();
	postNewComment(comment, localStorage.getItem("user_id"), slug);
};

const postNewComment = (comment, user, slug) => {
	fetch("http://localhost:4000/event/comment", {
		method: "POST",
		body: JSON.stringify({ comment, user, slug }),
		headers: {
			Accept: "application/json",
			"Content-Type": "application/json",
		},
	})
		.then((res) => res.json())
		.then((data) => {
			if (data.message) {
				alert(data.message);
			}
		})
		.catch((err) => console.error(err));
};
```

### Registering for an event

When a user registers for an event, the user's id is added to the `attendees` property (array) on the particular event. Although, you need to check if the user hasn't registered before you update the `attendees` property.

Add a POST route to the server that carries out the function.

```jsx
app.post("/register/event", async (req, res) => {
	const { userID, eventID } = req.body;

	for (let i = 0; i < events.length; i++) {
		if (events[i].id === eventID) {
			const validate = events[i].attendees.filter((user) => user === userID);
			if (validate.length === 0) {
				events[i].attendees.push(user);

				return res.json({ message: "Registered successfully!✅" });
				}
			} else {
				return res.json({ message: "You cannot register twice ❌" });
			}
		}
	}
});
```

The code snippet above accepts the event and user id from the React app, filters the events array via the id, and updates the attendee's list on the event with the matching id.

Execute the function below when a user clicks the Register button.

```jsx
const eventRegister = (user, id) => {
	fetch("http://localhost:4000/register/event", {
		method: "POST",
		body: JSON.stringify({ user, id }),
		headers: {
			Accept: "application/json",
			"Content-Type": "application/json",
		},
	})
		.then((res) => res.json())
		.then((data) => {
			if (data.message) {
				alert(data.message);
				navigate("/");
			}
		})
		.catch((err) => console.error(err));
};
```

### Retrieving users' events

Within the `Dashboard` component, you can display all the events the user registered for.

First, create an endpoint that accepts the user's id, loops through the entire event, and returns the user's events.

```jsx
app.post("/user/events", (req, res) => {
	const { userID } = req.body;
	let userEvents = [];
	for (let i = 0; i < events.length; i++) {
		let result = events[i].attendees.filter((user) => user === userID);
		if (result.length > 0) {
			userEvents.push(events[i]);
		}
	}
	res.json({ message: "Successful", events: userEvents });
});
```

Display the events returned from the server when the `Dashboard` page loads.

```jsx
const fetchMyEvents = (userID, setEvents) => {
	fetch("http://localhost:4000/user/events", {
		method: "POST",
		body: JSON.stringify({ userID }),
		headers: {
			Accept: "application/json",
			"Content-Type": "application/json",
		},
	})
		.then((res) => res.json())
		.then((data) => {
			if (data.message) {
				console.log(data);
				setEvents(data.events);
			}
		})
		.catch((err) => console.error(err));
};
```

Congratulations on making it thus far!🎉 In the upcoming section, you'll learn how to alert users when someone comments and registers for their events with Novu.

<hr />

## Trick 2: Adding notifications to your app ℹ️

Here, we need to notify the users when someone comments and registers for their events. Also, you can alert everyone when there is a new event.

To do this, we'll use [Novu](https://github.com/novuhq/novu) - an open-source notification infrastructure that enables you to send in-app, SMS, chat, push, and e-mail notifications from a single dashboard.

### Initiate it ⚡️

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

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

Enter your application name and sign in to your Novu dashboard. 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? Meetup Clone
? 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
✔ Created your account successfully.
```

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


![Notification template](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dhv0k4ujurq6mzmieyto.png)

Create a notification template with a workflow as shown below:


![different](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tw3vfgo76z2w41xeljhf.jpeg)


> 💡PS: You'll need to create three different notification templates to be triggered when someone comments on an event, registers for an event, and creates a new event.

[Novu Digest](https://docs.novu.co/platform/digest) allows you to control how you send notifications in your app. It collects multiple trigger events, schedules them, or sends them as a single message.

Update the In-App notification step to send this message to the event host when someone comments on their event.

```bash
{{user}} commented on your event.
```

![Novu notification bell](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqp1qepsf9as4thsfk30.png)

### Adding Novu notification bell to a React app

Novu in-app notification uses a notification bell to send alerts to users. Here, you'll learn how to add it to your React applications.

Install the Novu Notification package.

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

Create a `Novu.jsx` file within the components folder and copy the below into the file.

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

function Novu() {
    const navigate = useNavigate();
    const onNotificationClick = (notification) =>
        navigate(notification.cta.data.url);

    return (
        <>
            <NovuProvider
                subscriberId='<YOUR_SUBSCRIBER_ID>'
                applicationIdentifier='<YOUR_APP_ID>'
            >
                <PopoverNotificationCenter
                    onNotificationClick={onNotificationClick}
                    colorScheme='light'
                >
                    {({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />}
                </PopoverNotificationCenter>
            </NovuProvider>
        </>
    );
}
```

The code snippet above enables us to add Novu's notification bell icon to the application. With this, you can view all the notifications within the app.

Select Settings on your Novu Admin Panel to copy your App ID and replace the subscriber's ID placeholder with yours.

Import the `Novu.jsx` component into the `Nav.jsx`component.

```jsx
import meetup from "../assets/meetup.png"
import { Link } from "react-router-dom"
import Novu from "./Novu"

const Nav = () => {
  return (
      <nav className='navbar'>
          <Link to="/">
              <img src={meetup} alt="Meetup" className="logo"/>
          </Link>
          <div className="navBtn">
              <Novu />
              <button className="buttons signUpBtn">Log out</button>
          </div>
      </nav>
  )
}

export default Nav
```


![Configuring Novu on a Node.js server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fc02we6yf5xmdq1sap5r.png)


### Configuring Novu on a Node.js 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.

```jsx
const { Novu } = require("@novu/node");
const novu = new Novu("<YOUR_API_KEY>");
```

Create a function within the `index.js` file that sends notification to the event host via Novu.

```jsx
const addCommentNotification = async (userID) => {
	await novu.subscribers.identify(userID, {
		firstName: "inAppSubscriber",
	});
	const response = await novu.trigger("notify", {
		to: {
			subscriberId: "<YOUR_SUBSCRIBER_ID>",
		},
		payload: {
			user: userID,
		},
	});
	return response.data.data;
};
```

Execute the function when a user comments on an event.

```jsx
app.post("/event/comment", async (req, res) => {
	const { comment, user, slug } = req.body;
	for (let i = 0; i < events.length; i++) {
		if (events[i].slug === slug) {
			events[i].comments.unshift({
				user,
				id: generateID(),
				comment,
			});
			//👇🏻 sends notification via Novu
			const sendNotification = await addCommentNotification(user);
			if (sendNotification.acknowledged) {
				return res.json({ message: "Comment added successfully!✅" });
			}
		}
	}
});
```

Congratulations! You've completed the application.🎉

## Conclusion

So far, you've learnt how to authenticate users with SuperTokens, communicate between a React and Node.js app, and send in-app notifications using the Novu Digest.

[Novu](https://novu.co/) enables you to create a rich notification system in your applications, thereby providing a great user experience for your users. You should also try out [SuperTokens](https://supertokens.com/docs/guides) - it is easy to integrate and offers a seamless authentication process to your users.

The source code for this tutorial is available here:

https://github.com/novuhq/blog/tree/main/meetup-clone-react-supertokens-nodejs.

Thank you for reading!

<hr />

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