🪄✨Building a blog with a liking feature using React, Hanko and Novu 🔥
Tags: webdev, javascript, programming, react
Published: 2023-08-07 12:13:42

# TL;DR 🔥
In this tutorial, you'll learn how to build a blogging platform that let's you create and react to posts.

- We will build a login and registration with Hanko
- Build the entire blog:
 - Create posts
 - React to posts 
- Add in-app notification to every reaction with Novu.


![Blog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxbxah3y5itjzg1xzwkm.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 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 simple-blog
cd simple-blog
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.

```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.

```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 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 [here](https://github.com/novuhq/blog/blob/main/hanko-auth-blog-with-novu/client/src/index.css) into the `src/index.css` file.

<hr />

## Building the app user interface 🛠️

Here, we'll create the user interface for the blogging application to enable users to view, create, and react to posts.

Create a components folder within the `client/src` folder containing the `Home.jsx`, `Login.jsx`, `Details.jsx`, and `NewPost.jsx`.

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

- From the code snippet above
    - The `Home.jsx` component displays all the available posts.
    - The `Detail.jsx` component displays the details of each post, such as its content, the date posted, and the number of reactions to the post.
    - The `NewPost.jsx` component enables users to create a new post.
    - The `Login.jsx` component log users into the application via Hanko.

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

```jsx
import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Home from "./components/Home";
import Details from "./components/Details";
import Login from "./components/Login";
import NewPost from "./components/NewPost";

const App = () => {
	return (
		<Router>
			<Routes>
				<Route path='/' element={<Home />} />
				<Route path='/login' element={<Login />} />
				<Route path='/post/:slug' element={<Details />} />
				<Route path='/post/new' element={<NewPost />} />
			</Routes>
		</Router>
	);
};

export default App;
```

### The Home page

The Home page displays all the posts created within the application. Copy the code below into the `Home.jsx` file.

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

const Home = () => {
	return (
		<div>
			<nav className='navbar'>
				<Link to='/' className='logo'>
					<h2>MyBlog</h2>
				</Link>
				<div style={{ display: "flex", alignItems: "center" }}>
					<Link to='/post/new' className='newPostBtn'>
						New Post
					</Link>
				</div>
			</nav>
			<main className='main'>
				<h2 className='heading'>Latest Posts</h2>
				<div className='posts_container'>
					<Link to={`/post/details`} className='post'>
						<h2 className='post_title'>
							Building a chat app with React, Novu, and Websockets
						</h2>
					</Link>

					<Link to={`/post/details`} className='post'>
						<h2 className='post_title'>How to install Novu in React</h2>
					</Link>
				</div>
			</main>
		</div>
	);
};

export default Home;
```

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


### The Post Details page

This page displays post details when a user clicks on them from the `Home.jsx` component. Copy the code below into the `Details.jsx` file.

```jsx
import React from "react";
import { AiTwotoneLike, AiTwotoneDislike } from "react-icons/ai";

const Details = () => {
	return (
		<div>
			<header className='details_header'>
				<h1 className='details_heading'>How to install Novu in React</h1>
				<div className='post_details'>
					<div>
						<p className='details_date'>Posted on 30th July, 2023</p>
					</div>
					<div className='reactions-group'>
						<button className='reactBtn'>
							Like <AiTwotoneLike /> <span style={{ marginLeft: 5 }}>2</span>
						</button>
						<button className='reactBtn unlikeBtn'>
							Dislike <AiTwotoneDislike />
							<span style={{ marginLeft: 5 }}>1</span>
						</button>
					</div>
				</div>
			</header>
			<main className='details_body'>
				Lorem Ipsum is simply dummy text of the printing and typesetting
				industry. Lorem Ipsum has been the industry's standard dummy text ever
				since the 1500s, when an unknown printer took a galley of type and
				scrambled it to make a type specimen book. It has survived not only five
				centuries, but also the leap into electronic typesetting, remaining
				essentially unchanged. It was popularised in the 1960s with the release
				of Letraset sheets containing Lorem Ipsum passages, and more recently
				with desktop publishing software like Aldus PageMaker including versions
				of Lorem Ipsum.
			</main>
		</div>
	);
};

export default Details;
```


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


### The New Post page

This page displays a form field that accepts the title and content of a blog post. Copy the code snippet below into the `NewPost.jsx` file.

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

const NewPost = () => {
	const navigate = useNavigate();
	const [title, setTitle] = useState("");
	const [content, setContent] = useState("");

	const handleSubmit = (e) => {
		e.preventDefault();
		console.log({ title, content });
		setContent("");
		setTitle("");
	};
	return (
		<div>
			<nav className='navbar'>
				<Link to='/' className='logo'>
					<h2>MyBlog</h2>
				</Link>

				<div>
					<button className='newPostBtn logOut'>Log out</button>
				</div>
			</nav>
			<main className='main'>
				<h2 className='heading'>Create new post</h2>
				<form className='newPost_form' onSubmit={handleSubmit}>
					<label htmlFor='title' className='label'>
						Title
					</label>
					<input
						type='text'
						className='newPost_title'
						id='title'
						name='title'
						value={title}
						required
						onChange={(e) => setTitle(e.target.value)}
					/>
					<label htmlFor='content' className='label'>
						Content
					</label>
					<textarea
						rows={10}
						className='newPost_content'
						value={content}
						required
						onChange={(e) => setContent(e.target.value)}
					/>
					<button className='newPostBtn submitBtn' type='submit'>
						Create Post
					</button>
				</form>
			</main>
		</div>
	);
};

export default NewPost;
```

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

<hr />

## Are passkeys the future? 🔑

[Hanko](https://www.hanko.io/) is an open-source, easy-to-integrate authentication solution that enables you to add various forms of authentication such as Email & Password, password-less, passkeys, and OAuth to your software applications.

It is an all-in-one authentication solution that enables you to set up authentication in a few minutes in your web applications. It also provides customisable web components which you can add to your web application to handle authentication quickly and easily.

In the upcoming sections, you'll learn how to add Hanko to the blogging application.

### Adding authentication easily to React apps with Hanko

Here, you'll learn how to add authentication to your React applications using Hanko. Before we begin, install the Hanko package by running the code snippet below.

```bash
npm install @teamhanko/hanko-elements
```

### Setting up an Hanko project

Visit the [homepage](https://www.hanko.io/) and create an account.

Create a new organization that will manage your Hanko projects.


![Setting up an Hanko project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6f8slkz0ipy0yqlngn1q.png)

Then, create a new Hanko project and add your development server as the API URL.


![new Hanko project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mi5leog9wfylmx2chzie.png)


Finally, save your API URL somewhere on your computer; it will be used for setting up the authentication.


![API URL](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m2nw7gcywzam8ozar11q.png)

### Adding Hanko to React apps

Copy the code below into the `Login.jsx` file.

```jsx
import React, { useEffect, useCallback, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { register, Hanko } from "@teamhanko/hanko-elements";
const hankoApi = "<YOUR_HANKO_API_URL>";

const Login = () => {
	const navigate = useNavigate();
	const hanko = useMemo(() => new Hanko(hankoApi), []);

	useEffect(() => {
		register(hankoApi).catch((error) => {
			console.log(error);
		});
	}, []);

	return (
		<div className='login_container'>
			<hanko-auth />
		</div>
	);
};

export default Login;
```

The code snippet displays the Hanko authentication component and enables users to sign up or sign in directly via Hanko.

![code snippet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ellvvn6u5c2jgvqmwrte.png)

Add the code snippet below within the `Login.jsx` component.

```jsx
//👇🏻 generates random string as ID
const generateUserID = () => Math.random().toString(36).substring(2, 10);

//👇🏻 executes after a user logs in
const redirectAfterLogin = useCallback(() => {
	localStorage.setItem("loggedIn", "true");
	if (!localStorage.getItem("u_id")) {
		localStorage.setItem("u_id", generateUserID());
	}
	navigate("/");
}, [navigate]);

//👇🏻 triggered after a successful sign in
useEffect(
	() =>
		hanko.onAuthFlowCompleted(() => {
			redirectAfterLogin();
		}),
	[hanko, redirectAfterLogin]
);
```

From the code snippet above, when a user signs into the application, the `u_id` value is set to the local storage to identify each user when they request the Node.js server.

> 💡PS: I'm using local storage because this is a small application. If you are using Hanko in a production environment, you may need to [check out the backend guide](https://docs.hanko.io/guides/backend).
> 

Congratulations!🎉 You've successfully added Hanko to a React application. In the upcoming section, we'll add all the necessary features to the blogging application.

## Communicating with the Node.js server

In this section, you'll learn how to communicate with the Node.js server by retrieving and creating posts 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
```

### Displaying the blog posts

Create a posts array within the `index.js` file on the server.

```jsx
let posts = [
	{
		u_id: "a123",
		post_id: "1",
		title: "Building a chat app with NextJS and Novu",
		slug: "building-a-chat-app-with-nextjs-and-novu",
		content:
			"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.",
		published_date: "27-07-2023",
		likes: [{ u_id: "12345" }, { u_id: "ancsd" }],
		dislikes: [{ user_id: "12345" }, { u_id: "12345" }],
	},
	{
		u_id: "b123",
		post_id: "2",
		title: "How to create an ecommerce app with NextJS and Novu ",
		slug: "how-to-create-an-ecommerce-app-with-nextjs-and-novu",
		content:
			"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets.",
		published_date: "27-07-2023",
		likes: [{ u_id: "12345" }],
		dislikes: [{ user_id: "12345" }],
	},
];
```

Add another endpoint that returns the posts in JSON format.

```jsx
app.get("/posts", (req, res) => {
	res.json({
		posts,
	});
});
```

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

```jsx
export const fetchAllPosts = (setLoading, setPosts) => {
	fetch("http://localhost:4000/posts")
		.then((res) => res.json())
		.then((data) => {
			setLoading(false);
			setPosts(data.posts);
		})
		.catch((err) => console.error(err));
};
```

Finally, execute the function when the Home component mounts.

```jsx
import React, { useCallback, useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { fetchAllPosts } from "../utils/util";

const Home = () => {
	const [loggedIn, setLoggedIn] = useState(false);
	const [posts, setPosts] = useState([]);
	const [loading, setLoading] = useState(true);

	const fetchPosts = useCallback(() => {
		fetchAllPosts(setLoading, setPosts);
	}, []);

	useEffect(() => {
		if (localStorage.getItem("loggedIn")) {
			setLoggedIn(true);
		}
		fetchPosts();
	}, [fetchPosts]);

	if (loading) return <p>Loading...</p>;

	return (
		<div>
			<nav className='navbar'>
				<Link to='/' className='logo'>
					<h2>MyBlog</h2>
				</Link>
				<div style={{ display: "flex", alignItems: "center" }}>
					{loggedIn ? (
						<Link to='/post/new' className='newPostBtn'>
							New Post
						</Link>
					) : (
						<Link to='/login' className='newPostBtn'>
							Log in
						</Link>
					)}
				</div>
			</nav>
			<main className='main'>
				<h2 className='heading'>Latest Posts</h2>
				<div className='posts_container'>
					{posts?.map((post) => (
						<Link to={`/post/${post.slug}`} className='post' key={post.post_id}>
							<h2 className='post_title'>{post.title}</h2>
						</Link>
					))}
				</div>
			</main>
		</div>
	);
};

export default Home;
```

The code snippet above fetches all the posts from the server when the page mounts and displays them within the React app. It also checks if the user is authenticated to display either the `Login` or `New Post` buttons.

### Retrieving the posts' details

Here, you need to fetch a post's details when you click on it from the Home page. To do this, you need to filter the array of posts via its slug property.

Create another POST route that filters the `posts` array by a post's slug and returns the entire post object.

```jsx
app.post("/post/details", (req, res) => {
	const { slug } = req.body;
	const result = posts.filter((post) => post.slug === slug);
	res.json({ post: result[0] });
});
```

Add a function within the `utils/util.js` file that sends a request to the `post/details` endpoint and returns the post object.

```jsx
export const fetchPostContent = (slug, setLoading, setPost) => {
	fetch("http://localhost:4000/post/details", {
		method: "POST",
		body: JSON.stringify({ slug: slug }),
		headers: {
			Accept: "application/json",
			"Content-Type": "application/json",
		},
	})
		.then((res) => res.json(res))
		.then((data) => {
			setLoading(false);
			setPost(data.post);
		})
		.catch((err) => console.error(err));
};
```

Import the function into the `Details.jsx` component.

```jsx
import { useParams } from "react-router-dom";
import { fetchPostContent } from "../utils/util";

const Details = () => {
	const { slug } = useParams();
	const [post, setPost] = useState({});
	const [loading, setLoading] = useState(true);

	const fetchPostDetails = useCallback(() => {
		fetchPostContent(slug, setLoading, setPost)
	}, [slug]);

	useEffect(() => {
		fetchPostDetails();
	}, [fetchPostDetails]);

    if (loading) return <p>Loading...</p>;

    return (
        <div>....</div>
    )
```

Update the UI elements to display the post details accordingly.

```jsx
return (
	<div>
		<header className='details_header'>
			<h1 className='details_heading'>{post.title}</h1>
			<div className='post_details'>
				<div>
					<p className='details_date'>Posted on {post.published_date}</p>
				</div>
				<div className='reactions-group'>
					<button
						className='reactBtn'
						onClick={() => reactToPost(slug, "like")}
					>
						Like <AiTwotoneLike />{" "}
						<span style={{ marginLeft: 5 }}>{post.likes.length}</span>
					</button>
					<button
						className='reactBtn unlikeBtn'
						onClick={() => reactToPost(slug, "dislike")}
					>
						Dislike <AiTwotoneDislike />
						<span style={{ marginLeft: 5 }}>{post.dislikes.length}</span>
					</button>
				</div>
			</div>
		</header>
		<main className='details_body'>{post.content}</main>
	</div>
);
```


![Like Button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ywwf9zq0vdg10da3qo8n.gif)


### Reacting to blog posts

First, you need to create an endpoint on the Node.js server that updates the number of likes and dislikes property of a post when a user clicks the button from the user interface.

```jsx
app.post("/post/react", async (req, res) => {
	const { slug, type, u_id } = req.body;

	//👇🏻 like post functionality
	for (let i = 0; i < posts.length; i++) {
		if (posts[i].slug === slug && type === "like") {
			//👇🏻 validates the post reaction
			const validateLike = posts[i].likes.filter(
				(likes) => likes.u_id === u_id
			);
			if (validateLike.length === 0) {
				posts[i].likes.push({ u_id });
				res.json({ message: "You've just liked a post" });
			}
		}

		//👇🏻 dislike post functionality
		if (posts[i].slug === slug && type === "dislike") {
			//👇🏻 validates the post reaction
			const validateDislike = posts[i].dislikes.filter(
				(dislikes) => dislikes.u_id === u_id
			);
			if (validateDislike.length === 0) {
				posts[i].dislikes.push({ u_id });
				const sendNotifcation = await notify("liked", u_id);
				res.json({ message: "You've just disliked a post" });
			}
		}
	}
});
```

The code snippet above handles the user's reaction to posts. It filters the `posts` array via the post's slug and validates the post reaction to ensure that the user has not reacted to the post, before updating the property accordingly.

Create a function within the `utils/util.js` file that sends a request to the endpoint when a user clicks the Like and Dislike buttons.

```jsx
export const postReaction = (slug, type) => {
	fetch("http://localhost:4000/post/react", {
		method: "POST",
		body: JSON.stringify({ slug, type, u_id: localStorage.getItem("u_id") }),
		headers: {
			Accept: "application/json",
			"Content-Type": "application/json",
		},
	})
		.then((res) => res.json(res))
		.then((data) => alert(data.message))
		.catch((err) => console.error(err));
};
```

Execute the function when a user clicks on the buttons.

```jsx
import { postReaction } from "../utils/util";

const Details = () => {
	//👇🏻 calls the function
	const reactToPost = (slug, type) => {
		postReaction(slug, type);
	};

	return (
		<div>
			<header className='details_header'>
				<h1 className='details_heading'>{post.title}</h1>
				<div className='post_details'>
					<div>
						<p className='details_date'>Posted on {post.published_date}</p>
					</div>
					<div className='reactions-group'>
						{/*-- like button*/}
						<button
							className='reactBtn'
							onClick={() => reactToPost(slug, "like")}
						>
							Like <AiTwotoneLike />{" "}
							<span style={{ marginLeft: 5 }}>{post.likes.length}</span>
						</button>
						{/*-- Dislike button*/}
						<button
							className='reactBtn unlikeBtn'
							onClick={() => reactToPost(slug, "dislike")}
						>
							Dislike <AiTwotoneDislike />
							<span style={{ marginLeft: 5 }}>{post.dislikes.length}</span>
						</button>
					</div>
				</div>
			</header>
			<main className='details_body'>{post.content}</main>
		</div>
	);
};
export default Details;
```

### Creating new posts

Create an endpoint that adds a new post to the `posts` array.

```jsx
//👇🏻 creates post slug
const createSlug = (text, id) => {
	let slug = text
		.trim()
		.toLowerCase()
		.replace(/[^\w\s-]/g, "");
	slug = slug.replace(/\s+/g, "-");
	return slug + "-" + id;
};

//👇🏻 generates a random string as ID
const generateID = () => Math.random().toString(36).substring(2, 10);

app.post("/post/add", (req, res) => {
	const { u_id, title, content, date } = req.body;
	const postObject = {
		u_id,
		post_id: generateID(),
		title,
		slug: createSlug(title, generateID()),
		content,
		published_date: date,
		likes: [],
		dislikes: [],
	};
	posts.unshift(postObject);
	res.json({ message: "Post added successfully!✅" });
});
```

The code snippet above creates a new post object and adds the newly created post to the `posts` array.

Add a function that sends a request to the endpoint within the `utils/util.js` file.

```jsx
export const addNewPost = (u_id, title, content, date, navigate) => {
	fetch("http://localhost:4000/post/add", {
		method: "POST",
		body: JSON.stringify({ u_id, title, content, date }),
		headers: {
			Accept: "application/json",
			"Content-Type": "application/json",
		},
	})
		.then((res) => res.json(res))
		.then((data) => {
			alert(data.message);
			navigate("/");
		})
		.catch((err) => {
			console.error(err);
			alert("Encountered an error ❌");
		});
};
```

Execute the function when the user submits the form within the `NewPost.jsx` file.

```jsx
//👇🏻 formates the date to a readable string
const formatDate = () => {
	const date = new Date();
	const day = String(date.getDate()).padStart(2, "0");
	const month = String(date.getMonth() + 1).padStart(2, "0");
	const year = date.getFullYear();
	return `${day}-${month}-${year}`;
};

//👇🏻 executes on form submit
const handleSubmit = (e) => {
	e.preventDefault();
	//👇🏻 adds the new post
	addNewPost(
		localStorage.getItem("u_id"),
		title,
		content,
		formatDate(),
		navigate
	);
	setContent("");
	setTitle("");
};
```

<hr />

## Sending in-app notifications with Novu 📳

Here, we need to notify the post authors when someone reacts to their posts. To do this, we'll use [Novu](https://novu.co/) - an open-source notification infrastructure that enables you to send in-app, SMS, chat, push, and e-mail notifications from a single dashboard.

### Creating a Novu project

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 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? 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
✔ Created your account successfully.
```

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


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

Create a notification template with a workflow as shown below:


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



[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 post author when someone reacts to their post.

```bash
You have a new {{reaction}} on your post.
```

![Reaction](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6adtcm5q8zsbv9png3c.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>
		</>
	);
}

export default Novu;
```

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 `Home.jsx` component.

```jsx
const Home = () => {
	return (
		<div>
			<nav className='navbar'>
				<Link to='/' className='logo'>
					<h2>MyBlog</h2>
				</Link>
				<div style={{ display: "flex", alignItems: "center" }}>
					{/*---👇🏻 Novu component👇🏻---*/}
					<Novu />
					{loggedIn ? (
						<Link to='/post/new' className='newPostBtn'>
							New Post
						</Link>
					) : (
						<Link to='/login' className='newPostBtn'>
							Log in
						</Link>
					)}
				</div>
			</nav>
			{/*--- other components ---*/}
		</div>
	);
};
```

![Novu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/umqe6kc32fy9q4y6nxu9.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 post author via Novu.

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

Execute the function when a user reacts to a post.

```jsx
app.post("/post/react", async (req, res) => {
	const { slug, type, u_id } = req.body;

	for (let i = 0; i < posts.length; i++) {
		if (posts[i].slug === slug && type === "like") {
			const validateLike = posts[i].likes.filter(
				(likes) => likes.u_id === u_id
			);
			if (validateLike.length === 0) {
				posts[i].likes.push({ u_id });
				//👇🏻 Triggers Novu
				const sendNotifcation = await notify("like", u_id);
				if (sendNotifcation.acknowledged) {
					res.json({ message: "You've just liked a post" });
				}
			}
		}
		if (posts[i].slug === slug && type === "dislike") {
			const validateDislike = posts[i].dislikes.filter(
				(dislikes) => dislikes.u_id === u_id
			);
			if (validateDislike.length === 0) {
				posts[i].dislikes.push({ u_id });
				//👇🏻 Triggers Novu
				const sendNotifcation = await notify("dislike", u_id);
				if (sendNotifcation.acknowledged) {
					res.json({ message: "You've just disliked a post" });
				}
			}
		}
	}
});
```

Congratulations! You've completed the application.


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

<hr />

## Conclusion

So far, you've learnt how to authenticate users with Hanko, 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 [Hanko](https://www.hanko.io/) - it is minimal and easy to integrate.

The source code for this tutorial is available here:

https://github.com/novuhq/blog/tree/main/hanko-auth-blog-with-novu.

Thank you for reading!

<hr />

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