dev.to is a very popular community for developers. It’s a platform for thousands of developers to collaborate, learn, publish and explore new ways of making programming languages and technology work for them.
I’m an avid reader of dev.to and publish a lot of articles on the platform. My favorite thing to do is to check my notifications to see who posted a new article, commented or liked any of my posts.
I'll guide you on how to swiftly build an In-App Notification system for your next app using Novu and the dev.to API. While it might not exactly resemble the image mentioned above, it'll have many similarities.
If you want to explore the code right away, you can view the completed code on GitHub.
Grab your dev.to API Key
You can get your dev.to API key from your settings page.
Set Up a Next.js App
To create a Next.js app, open your terminal, cd
into the directory you’d like to create the app in, and run the following command:
npx create-next-app@latest devto
Go through the prompts, select your preference, install and run the app in your browser with:
npm run dev
Set Up & Integrate Novu
Run the following command to install the Novu node SDK:
npm install @novu/node
Run the following command to install the Novu Notification Center package:
npm install @novu/notification-center
The Novu Notification Center package provides a React component library that adds a notification center to your React app. The package is also available for non-React apps.
Before we can start sending and receiving notifications, we need to set up a few things:
- Create a workflow for sending notifications,
- Create a subscriber - recipient of notifications.
Create a Novu Workflow
A workflow is a blueprint for notifications. It includes the following:
- Workflow name and Identifier
- Channels: - Email, SMS, Chat, In-App and Push
Create a workflow by following the steps below:
- Click Workflow on the left sidebar of your Novu dashboard.
- Click the Add a Workflow button on the top left. You can select a Blank workflow or use one of the existing templates.
- The name of the new workflow is currently “Untitled”. Rename it to
Devto Notifications
. - Select In-App as the channel you want to add.
5. Click on the recently added “In-App” channel and add the following text to it. Once you’re done, click “Update” to save your configuration.
The code below is what is in the image above. You can copy & paste it into the Novu editor.
<p><strong>{{ name }}</strong> made a new post.</p>
<br />
<article style="padding:10px;border-radius:5px;border-color: white;border-block: solid;">
<a href="{{ article_url }}" style="font-weight:bold;">
<h3>{{ article_title }}</h3>
</a>
</article>
Enable the “Add an Avatar” button to allow the notifications coming in show a default avatar.
Create a Subscriber to receive Notifications
A subscriber is the recipient of a notification. Essentially, your app users are subscribers. As a logged-in user on dev.to, you are a subscriber.
To send notifications to a user on your app, you’ll need to register that user as a subscriber on Novu. If you click “Subscriber” on the left sidebar of the Novu dashboard, you’ll see the subscriber list. As a first time Novu user, it will be an empty list.
Open your terminal and run the following script to create a subscriber:
curl --location '<https://api.novu.co/v1/subscribers>' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: ApiKey <NOVU_API_KEY>' \
--data-raw '{
"firstName": "John",
"lastName": "Doe",
"email": "johndoe@domain.com",
"phone": "+1234567890"
}'
Note: Grab your NOVU API Key from the settings section of your Novu dashboard.
Refresh the Subscribers page on your Novu dashboard. You should see the recently added subscriber now! You can also add a subscriber to Novu by running this API endpoint.
Note: The best option to add a subscriber is via code in your backend. With Node.js code, you can run the following code to create a subscriber:
import { Novu } from "@novu/node";
// Insert your Novu API Key here
const novu = new Novu("<NOVU_API_KEY>");
// Create a subscriber on Novu
await novu.subscribers.identify("132", {
email: "john.doe@domain.com",
firstName: "John",
lastName: "Doe",
phone: "+13603963366",
});
For the sake of this article, we’ll make do with adding a subscriber via the terminal to speed the process up.
Set up Novu Notification Center in the App
Head over to scr/pages/index.js
. We’ll modify this page to include the Novu Notification Center.
Import the Notification Center components from the Novu notification center package like so:
...
import {
NovuProvider,
PopoverNotificationCenter,
NotificationBell,
} from "@novu/notification-center";
Display the Notification Center by adding the imported components like so:
...
return (
<main
className={`flex min-h-screen flex-col items-center justify-between p-24 ${inter.className}`}
>
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
Dev.to In-App Notifications
</p>
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
<NovuProvider
subscriberId={process.env.NEXT_PUBLIC_SUBSCRIBER_ID}
applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID}
>
<PopoverNotificationCenter>
{({ unseenCount }) => (
<NotificationBell unseenCount={unseenCount} />
)}
</PopoverNotificationCenter>
</NovuProvider>
</div>
</div>
...
<div className="grid grid-cols-2">
Check your app, you should immediately see a notification bell.
Click the notification bell. The Notification Center will immediately pop up like so:
Add the following to your .env
values:
NEXT_PUBLIC_SUBSCRIBER_ID=
NEXT_PUBLIC_NOVU_APP_ID=
NEXT_PUBLIC_NOVU_API_KEY=
Grab your Novu API Key and APP ID from the Settings section of your Novu dashboard.
Get the Identifier of the subscriber we created earlier and add it to the .env
values.
In a real world app, the ID of the logged-in user should be passed to the subscriberID property of the NovuProvider component.
Note: We’re using it as an env variable because we’re not incorporating an authentication system in this tutorial. Ideally, once the user registers, the user ID should immediately be created as a subscriber and the ID be passed to the component.
Reload your app and the notification center should be squeaky clean like so:
Build the API to Publish dev.to Articles & Trigger In-App Notifications
Create a publish-devto-article.js
file in the src/pages/api
directory.
Add the code below to it to import the Novu SDK and specify the workflow trigger ID:
import { Novu } from "@novu/node";
const novu = new Novu(process.env.NEXT_PUBLIC_NOVU_API_KEY);
export const workflowTriggerID = "devto-notifications";
Note: The @novu/node
Novu SDK can be used only in server-side.
The workflowTriggerID
value is obtained from the workflow dashboard. Earlier, when we set up a workflow titled Devto Notifications
, Novu created a slug from this title to serve as the trigger ID.
You can see it here:
Now add the following functions to the code in the publish-devto-article.js
file:
...
export default async function handler(req, res) {
const { article } = req.body;
const response = await fetch("<https://dev.to/api/articles>", {
method: "POST",
headers: {
"Content-Type": "application/json",
"API-Key": process.env.NEXT_PUBLIC_DEVTO_API_KEY,
},
body: JSON.stringify({ article: article }),
});
/**
* Get response of the published article from Dev.to
*/
const dataFromDevto = await response.json();
/**
* Extra the essential details needed from the Dev.to response
*/
const { title, url, published_timestamp, user } = dataFromDevto;
/**
* Send notification that a new article has been published
*/
sendInAppNotification(user, title, url);
return res.json({ message: dataFromDevto });
}
/**
* SEND IN-APP NOTIFICATION VIA NOVU
* @param {*} userDetails
* @param {*} articleTitle
* @param {*} articleURL
* @returns json
*/
async function sendInAppNotification(userDetails, articleTitle, articleURL) {
await novu.trigger(workflowTriggerID, {
to: {
subscriberId: process.env.NEXT_PUBLIC_SUBSCRIBER_ID,
},
payload: {
name: userDetails.name,
article_url: articleURL,
article_title: articleTitle,
profile_image: userDetails.profile_image_90,
},
});
return res.json({ finish: true });
}
The sendInAppNotification
function block of code above triggers the notification via the Novu SDK:
- Accepts the workflow trigger ID to determine which workflow to trigger.
- Accepts the subscriber ID value to identify the notification recipient.
- Accepts a payload object that represents the parameters to inject into the workflow variables.
The handler
function block above makes a POST request to Dev.to to publish an article and then calls the sendInAppNotification
to fire an In-App notification indicating that someone just published an article.
Note: The request body is passed from POSTMAN or Insomnia. Next, we will test our API with any of these API software tools.
Test the API with POSTMAN or Insomnia - Publish to Dev.to
Fire up either POSTMAN or Insomnia and make a request to the API we created and post a JSON request to publish an article on Dev.to.
Our API url: http://localhost:3000/api/publish-devto-article
I personally use & prefer Insomnia because it has a snappy and lovely UI. As you can see below, we passed some parameters (pulled from dev.to API docs) to enable us publish an article.
Next, check your app. You should see an instant notification about the new published article on Dev.to.
Click on the new post title. It should take you directly to the article on Dev.to!
You can try publishing as many as possible.
The Notification Center provides a lot of functions built-in. A user can mark all notifications as read, mark one notification as read, and delete a notification.
The Notification Center ships with a lot of props and options to customize the whole experience. Header, footer, colors can be customized to conform with your app.
Center the Notification Center, Just like Dev.to’s
You might be wondering, Dev.to’s Notification Center isn't a popover; it's centered on the page. How do I achieve that?
Simply replace the PopoverNotificationCenter
component with the NotificationCenter
component from the Novu package.
It should look something like this:
<NovuProvider
subscriberId={process.env.NEXT_PUBLIC_SUBSCRIBER_ID}
applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID}
>
<NotificationCenter />
</NovuProvider>
However, Next.js uses Server side rendering so you might come across this issue on your page because the <NotificationCenter />
component makes use of the window object.
Let’s fix this issue by wrapping the component in a way that disables ssr and lazy loads it.
Create a components/notifications
folder in the src/
directory. Go ahead and add these two files with the code below to them respectively:
index.js
export { default } from "./notifications";
notifications.js
import { NotificationCenter } from "@novu/notification-center";
import dynamic from "next/dynamic";
import React, { useCallback } from "react";
const Notifications = () => {
const onNotificationClick = useCallback((notification) => {
if (notification?.cta?.data?.url) {
window.location.href = notification.cta.data.url;
}
}, []);
return (
<div>
<NotificationCenter
colorScheme="dark"
onNotificationClick={onNotificationClick}
/>
</div>
);
};
export default dynamic(() => Promise.resolve(Notifications), {
ssr: false,
});
The code above does the following:
- It imports the original
<NotificationCenter />
component from Novu. - It uses Next’s dynamic functionality that allows us to dynamically load a component on the client side, and to use the
ssr
option to disable server-rendering. - It defines a notificationClick handler for the onNotificationClick prop of the NotificationCenter component (The function that is called when the notification item is clicked).
- The
colorScheme
is one of the props you can pass to the NotificationCenter component. It takes in either “light” or “dark” values to enable you set the UI mode.
Finally, head back to the pages/index.js
file, import our newly created component and use it in the NovuProvider
parent component like so:
...
import Notification from "../components/notifications";
...
...
...
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
<NovuProvider
subscriberId={process.env.NEXT_PUBLIC_SUBSCRIBER_ID}
applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID}
>
<Notification />
</NovuProvider>
</div>
...
...
...
Lastly, let's adjust the components' position to place the Notification Center in the middle of the page.
pages/index.js
import Image from "next/image";
import { Inter } from "next/font/google";
import {
NovuProvider,
PopoverNotificationCenter,
NotificationBell,
} from "@novu/notification-center";
import Notification from "../components/notifications";
const inter = Inter({ subsets: ["latin"] });
export default function Home() {
return (
<main
className={`flex min-h-screen flex-col items-center justify-between p-24 ${inter.className}`}
>
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
Dev.to In-App Notifications
</p>
</div>
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-full sm:before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full sm:after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700/10 after:dark:from-sky-900 after:dark:via-[#0141ff]/40 before:lg:h-[360px]">
<NovuProvider
subscriberId={process.env.NEXT_PUBLIC_SUBSCRIBER_ID}
applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID}
>
<Notification />
</NovuProvider>
</div>
<div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
<a
href="<https://nextjs.org/docs?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app>"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Docs{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
->
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Find in-depth information about Next.js features and API.
</p>
</a>
<a
href="<https://nextjs.org/learn?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app>"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Learn{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
->
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Learn about Next.js in an interactive course with quizzes!
</p>
</a>
<a
href="<https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app>"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Templates{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
->
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Discover and deploy boilerplate example Next.js projects.
</p>
</a>
<a
href="<https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app>"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Deploy{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
->
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50 text-balance`}>
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
</main>
);
}
Note: Ensure this is the full code of the pages/index.js
file to avoid missing anything.
Now, reload your app. It should resemble Dev.to's centered Notification page. Yaayyyy!!
Publish a new article on Dev.to and observe how the notifications arrive via the centralized Notifications component, even without a notification bell. The Notification unseen counter shows right there.
Go one step further, make the background light mode & change the Notification Center to light mode.
Up Next: Build Dev.to Email Community Digest
You've learned how to develop an In-App Notification System in under 30 minutes. Over time, I've learned that the best developers use battle-tested tools to build their apps and systems.
Fortunately, there's more. I have additional tips to share with you. 😉
Next, I'll demonstrate how to use Novu to build an Email Community Digest, similar to the excellent digest from Dev.to, which keeps us updated on the top published articles.
Stay tuned for the next part of this article!
A reminder to check out the completed code for this article on GitHub. Let me know if you run into any issues at all.
Don't hesitate to ask any questions or request support. You can find me on Discord and Twitter. Please feel free to reach out.