How to build dev.to In-App Notification System in 20 minutes

Prosper Otemuyiwa - Apr 23 - - Dev Community

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.

GuideI'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 UpSet 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
Enter fullscreen mode Exit fullscreen mode

Go through the prompts, select your preference, install and run the app in your browser with:

Prompts

npm run dev
Enter fullscreen mode Exit fullscreen mode

Set Up & Integrate Novu

Run the following command to install the Novu node SDK:

npm install @novu/node
Enter fullscreen mode Exit fullscreen mode

Run the following command to install the Novu Notification Center package:

npm install @novu/notification-center
Enter fullscreen mode Exit fullscreen mode

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:

  1. Create a workflow for sending notifications,
  2. 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:

  1. Click Workflow on the left sidebar of your Novu dashboard.
  2. Click the Add a Workflow button on the top left. You can select a Blank workflow or use one of the existing templates.
  3. The name of the new workflow is currently “Untitled”. Rename it to Devto Notifications.
  4. Select In-App as the channel you want to add.

In-App Channel5. 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.

Add text

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>
Enter fullscreen mode Exit fullscreen mode

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.

Create a SubscriberOpen 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"
    }'
Enter fullscreen mode Exit fullscreen mode

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",
});
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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">
Enter fullscreen mode Exit fullscreen mode

Check your app, you should immediately see a notification bell.

Notification bellClick the notification bell. The Notification Center will immediately pop up like so:

Env variablesAdd the following to your .env values:

NEXT_PUBLIC_SUBSCRIBER_ID=
NEXT_PUBLIC_NOVU_APP_ID=
NEXT_PUBLIC_NOVU_API_KEY=
Enter fullscreen mode Exit fullscreen mode

Grab your Novu API Key and APP ID from the Settings section of your Novu dashboard.

Get the Subscriber IDGet 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 APIBuild 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";
Enter fullscreen mode Exit fullscreen mode

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:

Add the code belowNow 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 });
}
Enter fullscreen mode Exit fullscreen mode

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.

Instant NotificationNext, check your app. You should see an instant notification about the new published article on Dev.to.

link to articleClick on the new post title. It should take you directly to the article on Dev.to!

Publish awayYou can try publishing as many as possible.

Notification CenterThe 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>
Enter fullscreen mode Exit fullscreen mode

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.

SSR issuesLet’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"; 
Enter fullscreen mode Exit fullscreen mode

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,
});
Enter fullscreen mode Exit fullscreen mode

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>
...
...
...
Enter fullscreen mode Exit fullscreen mode

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">
              -&gt;
            </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">
              -&gt;
            </span>
          </h2>
          <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
            Learn about Next.js in an interactive course with&nbsp;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">
              -&gt;
            </span>
          </h2>
          <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
            Discover and deploy boilerplate example Next.js&nbsp;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">
              -&gt;
            </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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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!!

Centered NotificationsPublish 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.

Change to light modeGo one step further, make the background light mode & change the Notification Center to light mode.

Email Community DigestUp 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.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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .