TL;DR
In this article, you'll learn how to add a notification system to any app in just a few simple steps! The primary focus in making this app was to showcase just how simple it is to add notifications to an app.
If you take any modern app, there's a high chance that it would have notifications functionality baked in. And why not!
Notifications make any app better by providing real-time updates and keeping users engaged. They help increase user retention by providing a medium of communication between an app and its users.
If you're reading this article, chances are that you already understand the importance of having notifications in your app and are looking for a way to add a notification system to your app.
Well, you've come to the right place. In this tutorial, we'll have a look at how we can add notifications to any app. Now, writing a custom solution every time you want to add notifications somewhere is cumbersome, I know.
But we'll use a magical tool to help make our jobs easier and believe me, this whole thing will be much simpler than you might think, pinky promise!
How, you ask?
Enter Novu!
Novu helped me solve the biggest problem I had every time I wanted to add notifications to an app - writing a notifications center from scratch.
And even though, I could re-use parts of what I'd written earlier, the unique use cases of each app ensured, I had to make significant changes to my previous system.
With Novu, I could simply grab an API key and use the custom component to add notifications to any web app. PERIOD!
Novu - Open-source notification infrastructure for developers:
Novu is an open-source notification infrastructure for developers. It helps you manage all the product notifications be it an in-app notification (a bell icon like what's there in Facebook), Emails, SMSs, Discord, and what not.
I'll be super happy if you check us out on GitHub and give us a star! ❤️
https://github.com/novuhq/novu
Let's write some code:
We'll make our app in two stages - backend and front-end. Both will live in separate GitHub repositories and I'll also show you how to deploy both to the web, enabling us to access the app from anywhere.
Let's start with the backend.
Basic set-up:
We'll start with an empty git repo. Create an empty git repository, enter all the relevant details, and publish it to GitHub. Then open it in your IDE (I'm going to use VS Code):
Our first step here would be to install all the required packages. We'll rely on several packages from npm (Node Package Manager).
To start this process, we'll generate a package.json using the following command:
bash
npm init -y
This command generates the package.json file and now we can install all the packages we need. Use the following command to install the packages we'll be using:
bash
npm i novu body-parser cors dotenv express mongoose nodemon
Now, create a '.env' file and a '.gitignore' file in the root of the project. We'll keep sensitive data like our MongoDB connection URL and the Novu API key in the .env file. To stop it from getting staged by git and being pushed onto GitHub, add the .env file to the .gitignore file, as shown below. This way it'll be accessible to us in the app but nobody will be able to see it on Github.
Connecting to a database:
After completing the basic setup, it is now time for us to connect to a database.
If you haven't already, create your MongoDB account and sign in.
We'll need to get a connection URL from our database and will plug that into our backend. To get that URL, go to the top left corner and create a new project.
Give your project a name and then click the 'build a database' button:
After this, choose the free option and leave everything else to default.
Now, enter the username and password that you want to use for database authentication, and make sure that you've noted down the password (we're going to need it).
Now, only the last step is left which is to obtain our connection URL. To get it, click on the 'connect' button
Then choose the 'compass' option.
Now, note down the URL (highlighted text in the image):
In this URL, you'll have to replace '' with your actual password (that you noted down above). That's it!
Store this URL in a variable in your '.env' file as shown below:
Obtaining Novu API key:
Getting the Novu API key is quite simple. You just need to head over to Novu's web platform
Then create your account and sign in to it. Then go to 'Settings' from the left navigation menu:
Now, in the settings, go to the second tab called 'API Keys'. There, you'll see your Novu API key. Copy it and add it to the .env file in your project's root directory:
Let's start coding:
The backend for this app is rather simple and consists of a few key parts:
- Controller: It contains code for the function that runs when we make server requests from our front-end. It just contains one simple function:
import { inAppNotification } from "../Novu/novu.js";
import Notif from "../models/notif.js";
export const createNotif = async (req, res) => {
const { description } = req.body
const newNotif = new Notif({
description
});
try {
await newNotif.save();
await inAppNotification(description, "Sumit");
res.status(201).json(newNotif);
} catch (error) {
res.status(409).json({ message: error });
}
}
The function starts by destructuring the description property from the req.body object, which is passed in as a parameter when the function is called from the front-end.
Next, a new instance of the 'Notif' model is created using the description value, which is then saved to a database using the 'save()' method.
After the notification is saved to the database, an in-app notification is sent using the 'inAppNotification' function, which is imported from the "../Novu/novu.js" module.
This function takes two arguments:
- The description of the notification, and
- The name of the user who triggered the notification, in this case, it is my name: "Sumit".
If everything is successful, the function sends an HTTP status code of 201 along with the new notification object in the response.
On the contrary, if an error occurs during the process, the function sends an HTTP status code of 409 along with an error message in the response.
Database Schema:
In MongoDB, data is stored in things called 'Collections'. Collections are like boxes within which we store data. A database in this case is the room. So the database contains entities called 'collections' and we store data within those collections.
The schema for notifications is as follows:
import mongoose from "mongoose";
const notifSchema = mongoose.Schema(
{
description: { type: String, required: true }
},
{
collection: "notif",
}
);
export default mongoose.model("Notif", notifSchema);
Here, we're defining a 'notif' collection in our database. We're using the 'Mongoose' library to connect to the database.
Using 'mongoose.Schema' method, we're creating a new schema object for the "notif" collection. The object passed as the first parameter is defining the shape of the documents in the collection.
In this case, the schema is defining a single field called "description", which is of type String and is required (i.e., it must be present in every document).
The second parameter being passed to mongoose.Schema is an optional 'options' object. In this case, it is setting the name of the collection to "notif".
Finally, we're using 'mongoose.model' method to create a model based on the schema. This model is being exported as the default export of this module.
The benefit of exporting it is that we can now use it to interact with the "notif" collection in the MongoDB database.
Now, off to the sweet part!
Setting up Novu notification template:
We're now left with two tasks in the backend:
- Configuring Novu, and
- Starting the server
To configure Novu, go the the web platform again and go to 'Notifications' from the left menu bar:
Now, create a new workflow, give it a name, and go to the workflow editor:
Since we'll be using just one functionality - In-app notifications, we'll set only that one up. But with Novu, you can send notifications through pretty much every channel out there, be it email, sms, push, or chat.
To setup the in-app channel, drag the button called 'in-app' from the right options menu and click on it to edit it.
Because we're using 'description' in the backend, we'll have to plug that in here as shown below:
Now, back to the code:
In the controller above, we had used a function called inAppNotification
but we never defined it. Now is the time to do so:
So, go to your project's root and create a new file. In that file, we'll create this function:
import { Novu } from '@novu/node';
export const inAppNotification = async (description, Id) => {
const novu = new Novu(process.env.NOVU_API_KEY);
await novu.subscribers.identify(Id, {
firstName: "inAppSubscriber",
});
await novu.trigger("in-app", {
to: {
subscriberId: "Sumit",
},
payload: {
description: description
},
});
};
Notice the use of 'description' here (that we set up in Novu) in the previous step.
Now, the last step of the back-end is remaining, which is setting up the server and the routes.
Our app is quite simple here and we need just one route:
import express from "express";
import { createNotif } from '../controller/notif.js'
const router = express.Router();
router.post('/', createNotif)
export default router;
And the server setup is also relatively straight forward:
import express from "express";
import mongoose from "mongoose"
import cors from "cors"
import bodyParser from "body-parser";
import dotenv from "dotenv";
import notifRoute from "./routes/notif.js"
dotenv.config();
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ limit: "30mb", extended: true }));
app.use(cors());
app.use("/home", notifRoute)
app.listen(3000, function () {
console.log('listening on 3000')
})
app.get('/', (req, res) => {
res.send('Project running!')
})
const CONNECTION_URL = process.env.CONNECTION_URL;
const PORT = process.env.PORT || 8000
app.use("/", (req,res) => res.send('this is working'))
mongoose.connect(CONNECTION_URL, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => app.listen(PORT, () => console.log(`server running on port: ${PORT}`)))
.catch((error) => console.log(error));
With these out of the way, our backend is done and we can now move to the front end.
Coding up the front-end:
In the front-end, we're gonna use the magical power of Web component. It is a collection of three things:
-
NovuProvider
, -
PopoverNotificationCenter
, and NotificationBell
These are all combined into one simple component, that we call Web Component. Web component is what enables us to embed this notification center into just about any web app.
You can read more about it here!
Now, in the front-end, we're gonna need four files:
- 'index.html' - This will contain the markup,
- 'styles.css' - This will contain the CSS styles,
- 'app.js' - This will contain all the logic to send notifications from front-end to back-end and fetch them all in the bell icon, and
- the '.env' file - This will contain our Novu API key
The contents of each one of them are as follows, starting with 'index.html' below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./styles.css">
<script src="https://novu-web-component.netlify.app/index.js"></script>
<title>Notification Generator</title>
</head>
<body>
<div class="header">
<h1>Notifications Generator</h1>
<notification-center-component style="display: inline-flex" application-identifier="SWMw97ec1ZNA"
subscriber-id="Sumit" class="bell"></notification-center-component>
</div>
<div class="content">
<img class="img" src="./notification-bell.png" alt="notification bell image" srcset="">
<form id="form" action="#" method="post">
<input class="input" type="text" placeholder="Enter notification text and click the send button!" name="description">
<button id="btn">Send</button>
</form>
</div>
<footer>
<p>Proudly powered by <a class="link" href="http://www.novu.co">Novu</a></p>
</footer>
<script src="./app.js"></script>
</body>
</html>
Then, the 'app.js' file is as follows:
const form = document.querySelector('#form')
// extracting send button
const btn = document.querySelector('#btn');
const input = document.querySelector('.input')
// btn.onclick = () => console.log('clicked');
form.addEventListener("submit", async (e) => {
e.preventDefault();
console.log('button clicked');
await fetch('https://notificationsgeneratorbackend.onrender.com/home', {
method: 'POST',
body: JSON.stringify({
description: input.value,
}),
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
input.value = ""
})
// extracting input value on button click
// here you can attach any callbacks, interact with the web component API
let nc = document.getElementsByTagName('notification-center-component')[0];
nc.onLoad = () => console.log('hello world!');
Finally, we'll have some CSS code for styling in the styles.css
file and the Novu API key in the .env
file.
You can find the CSS I've used here.
If you've done everything correctly, you should have an app that looks something like this:
In this app, we've demonstrated how to use the 'Web component' to add a notification center to any web app. To keep things simple, we've just used the in-app notification feature but you can use just about any notification medium you can imagine - from chat to sms to emails and more!
Deploying our front-end and our back-end:
The last step remaining is to deploy our front-end and our back-end. We're gonna use a service called 'Render' to deploy our back-end and the good, old 'Netlify' for our front-end:
Both are very straight forward and the process is a simple plug-and-play kinda thing: Just log in to both, point to your GitHub repo, and add the environment variables as defined in the '.env' file of both. That's it!
If you want to go over my code, it is available here:
Lastly, if you need help with anything and wanna reach out to me, I'm always available at the Novu discord. Feel free to join in and say hi! 👋
This entire project was made possible by the awesomeness of Novu and it takes me a lot of time and effort to come up with tutorials like this one, so if you could spare a moment and give us a star on our GitHub repo, it would mean a lot to me.
Thanks for all your support! ⭐⭐⭐⭐⭐
Don't forget to comment what you liked and didn't like about this tutorial.
Have a great one, bye! 👋