DEV Community

Cover image for Building the largest Notifications Library in the world using ChatGPT, React, and NodeJS 🤯
Nevo David for novu

Posted on • Originally published at novu.co

Building the largest Notifications Library in the world using ChatGPT, React, and NodeJS 🤯

TLDR;

I am going to show you how to create a notification library like on this website:
https://notifications.directory

I will show you how to create thousands of notifications with ChatGPT and render it with React.

Happy if you can help me out by pressing the "love" sign, it helps me to create more articles!

Love

Things you need to know

At the moment, he ChatGPT API isn't available.
The latest model from OpenAI is "text-davinci-003".
If you're okay with using an older model, go ahead and give it a try!

But keep in mind that it's not as advanced as the current ChatGPT model "text-davinci-002-render". In this tutorial, I'm going to show you a solution that was created by someone in the open-source community.

It's a good option for now, but please keep in mind that it might not always be available. OpenAI hasn't released its ChatGPT API yet, so this is just a temporary solution, this solution can't be used for commercial purposes.

Meme

What data do we need?

I will teach you how to do the same things we do in The notification generator project, simplified.

We will create three hierarchies.

  • Categories Website types like
    • SaaS
    • E-commerce
    • Online marketplace
    • Travel
    • Etc..
  • Notifications Types
    • SaaS: "Payment is due"
    • E-commerce: "Product out of stock", etc...
  • Notifications
    • “Hi NAME, your payment is due for DATE
    • “Hi NAME, product PRODUCT_NAME is out of stock.”

To create each hierarchy, we must have the data of the previous hierarchy.

While in Novu, we store everything inside MongoDB here we will do everything with a simple array.

Novu - the first open-source notification infrastructure

Just a quick background about us. Novu is the first open-source notification infrastructure. We basically help to manage all the product notifications. It can be In-App (the bell icon like you have in Facebook), Emails, SMSs and so on.

Novu

I would be super happy if you could give us a star! And let me also know in the comments ❤️
https://github.com/novuhq/novu

Register to ChatGPT

Go over to https://platform.openai.com/signup

Register to OpenAI, head over to this endpoint: https://chat.openai.com/api/auth/session,

and grab the accessToken from the JSON.

Access Token

Setting up the project

We will start by creating the project and create our main file.

mkdir backend
cd backend
npm init
touch index.js
Enter fullscreen mode Exit fullscreen mode

Let’s install our chatgpt library.

npm install @waylaidwanderer/chatgpt-api --save
Enter fullscreen mode Exit fullscreen mode

This library uses Esm and not CommonJS. Therefore, it might be problematic for you if you try to implement it with NestJS - if you need help, let me know in the comments.

I will not separate the code into multiple files, as I want to keep it simple for the sake of this tutorial. Open index.js, and let's start writing.

Let's initiate our ChatGPT. As you can see, the library is using "chatgpt.hato.ai*", a solution one of the community members found to use ChatGPT and was kind enough to deploy it on his server. While using this, your ChatGPT account will be exposed to an external resource, so please make sure you don't add any credit card information and that you are using the free ChatGPT version.
**Please be advised that the API is limited to 10 requests per second.
*

import { ChatGPTClient } from '@waylaidwanderer/chatgpt-api'

const api = new ChatGPTClient('YOUR ACCESS TOKEN FROM THE PREVIOUS STEP', {
  reverseProxyUrl: 'https://chatgpt.hato.ai/completions',
    modelOptions: {
      model: 'text-davinci-002-render',
  },
});
Enter fullscreen mode Exit fullscreen mode

Let’s create our database 🤣

const categories = []
Enter fullscreen mode Exit fullscreen mode

Final result of “categories” will look like this

[
  {
    "category": "SaaS",
    "notificationTypes": [
      {
        "name": "Payment is due",
        "notifications": [
          "Hi {{name}}, your payment is due for. {{date}}"
        ]
      }
    ]
  }
]
Enter fullscreen mode Exit fullscreen mode

Your code should look like this now:

import { ChatGPTClient } from '@waylaidwanderer/chatgpt-api'

const api = new ChatGPTClient('YOUR ACCESS TOKEN FROM THE PREVIOUS STEP', {
  reverseProxyUrl: 'https://chatgpt.hato.ai/completions',
    modelOptions: {
      model: 'text-davinci-002-render',
  },
});

const notifications = [];
Enter fullscreen mode Exit fullscreen mode

Let’s write the code that will generate all the categories.

We want the prompt and the result to look like this:

Chat1

The thing is that everything will come to us as a plain text.
We need a function that will create an array from the list.
I have created a quick one.
If you have something more efficient, let me know in the comments:

const extractNumberedList = (text) => {
    return text.split("\n").reduce((all, current) => {
        const values = current.match(/\d+\.(.*)/);
        if (values?.length > 1) {
            return [...all, values[1].trim()];
        }

        return all;
    }, []);
}
Enter fullscreen mode Exit fullscreen mode

Now the fun part.
We need all the possible categories, then run it through our function and map it to our database:

const res = await api.sendMessage('Can you list 50 types of websites in the world send in-app notifications? just the category');
categories.push(...extractNumberedList(res.response).map(p => ({category: p, notificationTypes: []})));
Enter fullscreen mode Exit fullscreen mode

Now we will iterate over the categories and start adding content to them. I am not going to use functional programming here for the first part.
I will use the “for loop” as it’s easier with async / await:

for (const category of categories) {

}
Enter fullscreen mode Exit fullscreen mode

Let’s start to create our notification types:

notification type

for (const category of categories) {
    const notificationTypesRes = await api.sendMessage(`I have a website of type ${category}, What kind of notifications should I sent to my users? can you just write the type without context? give me 20`);
  const notificationTypes = extractNumberedList(res.response).map(p => ({name: p, notifications: []}));
}
Enter fullscreen mode Exit fullscreen mode

And now, let’s add our notifications:

notifications

for (const category of categories) {
  // get all the notifications type
    const notificationTypesRes = await api.sendMessage(`I have a website of type ${category}, What kind of notifications should I sent to my users? can you just write the type without context? give me 20`);
  // parse all the notification type and map them
  const notificationTypes = extractNumberedList(notificationTypesRes.response).map(p => ({name: p, notifications: []}));
  // get all the notifications for the notification type
  const notifications = await Promise.all(notificationTypes.map(async p => {
        const notificationRes = await api.sendMessage(`I have built a system about "${category}" and I need to create in-app notifications about "${notificationType}", can you maybe write me a 20 of those? just the notification without the intro, use lower-case double curly braces with no spaces and underscores for the variables, and avoid using quotation when writing the notifications`);
        return {
            ...p,
            notifications: extractNumberedList(notificationRes.response)
        }
    }));
  // push it to the main array
    notificationTypes.notifications.push(...notifications);
}
Enter fullscreen mode Exit fullscreen mode

The final code will be looking like this:

import { ChatGPTClient } from '@waylaidwanderer/chatgpt-api'

const api = new ChatGPTClient('YOUR ACCESS TOKEN FROM THE PREVIOUS STEP', {
  reverseProxyUrl: 'https://chatgpt.hato.ai/completions',
    modelOptions: {
      model: 'text-davinci-002-render',
  },
});

const categories = [];

const extractNumberedList = (text) => {
    return text.split("\n").reduce((all, current) => {
        const values = current.match(/\d+\.(.*)/);
        if (values?.length > 1) {
            return [...all, values[1].trim()];
        }

        return all;
    }, []);
}

const res = await api.sendMessage('Can you list 50 types of websites in the world send in-app notifications? just the category');
categories.push(...extractNumberedList(res.response).map(p => ({category: p, notificationTypes: []})));

for (const category of categories) {
  // get all the notifications type
    const notificationTypesRes = await api.sendMessage(`I have a website of type ${category}, What kind of notifications should I sent to my users? can you just write the type without context? give me 20`);
  // parse all the notification type and map them
  const notificationTypes = extractNumberedList(notificationTypesRes.response).map(p => ({name: p, notifications: []}));
  // get all the notifications for the notification type
  const notifications = await Promise.all(notificationTypes.map(async p => {
        const notificationRes = await api.sendMessage(`I have built a system about "${category}" and I need to create in-app notifications about "${notificationType}", can you maybe write me a 20 of those? just the notification without the intro, use lower-case double curly braces with no spaces and underscores for the variables, and avoid using quotation when writing the notifications`);
        return {
            ...p,
            notifications: extractNumberedList(notificationRes.response)
        }
    }));
  // push it to the main array
    notificationTypes.notifications.push(...notifications);
}
Enter fullscreen mode Exit fullscreen mode

It can run in the background forever 🥳

We can quickly move it to a separate task / cron / queue, but we will not do it here.

While this thing is running in the background, let’s add here the API that our frontend can use

Let’s go back to our command line and write:

npm install express --save
Enter fullscreen mode Exit fullscreen mode

We will do the simplest thing and just serve our categories variable.

Feel free to take it to the next level with an actual database:


import express from 'express';
const app = express()
const port = 3000

app.get('/', (req, res) => {
  return categories;
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})
Enter fullscreen mode Exit fullscreen mode

And the complete code is as follows:


import { ChatGPTClient } from '@waylaidwanderer/chatgpt-api';
import express from 'express';

const categories = [];
const app = express();
const port = 3000;

const api = new ChatGPTClient('YOUR ACCESS TOKEN FROM THE PREVIOUS STEP', {
  reverseProxyUrl: 'https://chatgpt.hato.ai/completions',
    modelOptions: {
      model: 'text-davinci-002-render',
  },
});

const extractNumberedList = (text) => {
    return text.split("\n").reduce((all, current) => {
        const values = current.match(/\d+\.(.*)/);
        if (values?.length > 1) {
            return [...all, values[1].trim()];
        }

        return all;
    }, []);
}

app.get('/', (req, res) => {
  return categories;
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
});

const res = await api.sendMessage('Can you list 50 types of websites in the world send in-app notifications? just the category');
categories.push(...extractNumberedList(res.response).map(p => ({category: p, notificationTypes: []})));

for (const category of categories) {
  // get all the notifications type
    const notificationTypesRes = await api.sendMessage(`I have a website of type ${category}, What kind of notifications should I sent to my users? can you just write the type without context? give me 20`);
  // parse all the notification type and map them
  const notificationTypes = extractNumberedList(notificationTypesRes.response).map(p => ({name: p, notifications: []}));
  // get all the notifications for the notification type
  const notifications = await Promise.all(notificationTypes.map(async p => {
        const notificationRes = await api.sendMessage(`I have built a system about "${category}" and I need to create in-app notifications about "${notificationType}", can you maybe write me a 20 of those? just the notification without the intro, use lower-case double curly braces with no spaces and underscores for the variables, and avoid using quotation when writing the notifications`);
        return {
            ...p,
            notifications: extractNumberedList(notificationRes.response)
        }
    }));
  // push it to the main array
    notificationTypes.notifications.push(...notifications);
}
Enter fullscreen mode Exit fullscreen mode

NODE.JS ✅

Now let’s move over to react to display all of our categories.

I will do the simplest thing of aggregating everything inside of a <ul>

Let’s right some bash commands:

cd ..
npx create-react-app frontend
cd frontend
Enter fullscreen mode Exit fullscreen mode

I will be using axios to get all the results from the database, feel free to use fetch / react-query etc…

npm install axios
Enter fullscreen mode Exit fullscreen mode

Let’s open our main component at “src/App.js” and add some imports

import {useState, useCallback, useEffect} from 'react';
import axios from 'axios';
Enter fullscreen mode Exit fullscreen mode

Let’s open our main component at “src/App.js” and add a new state that will contain all of our database from the server:

const [categories, setCategories] = useState([]);
Enter fullscreen mode Exit fullscreen mode

And now, we will write the function that will get all the information from the server

const serverInformation = useCallback(async () => {
    const {data} = await axios.get('http://localhost:3000');
    setCategories(data);
}, []);
Enter fullscreen mode Exit fullscreen mode

Add it to our useEffect, that will run as soon as the component is loaded

useEffect(() => {
    serverInformation();
}, []);
Enter fullscreen mode Exit fullscreen mode

And now, we will render everything on the screen

<ul>
    {categories.map(cat => (
        <li>
            {cat.category}
            <ul>
                {cat.notificationTypes.map(notificationType => (
                    <li>
                        {notificationType.name}
                        <ul>
                            {notificationType.notifications.map(notification => (
                                <li>
                                    {notification}
                                <li>
                            )}
                        </ul>
                    <li>
                )}
            </ul>
        <li>
    )}
</ul>
Enter fullscreen mode Exit fullscreen mode

And that’s it! We have rendered the entire list!!!

The full code of App.js would look like this:

import {useState, useCallback, useEffect} from 'react';
import axios from 'axios';

function App() {
    const [categories, setCategories] = useState([]);

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

    const serverInformation = useCallback(async () => {
        const {data} = await axios.get('http://localhost:3000');
        setCategories(data);
    }, []);

    return (
        <ul>
            {categories.map(cat => (
                <li>
                    {cat.category}
                    <ul>
                        {cat.notificationTypes.map(notificationType => (
                            <li>
                                {notificationType.name}
                                <ul>
                                    {notificationType.notifications.map(notification => (
                                        <li>
                                            {notification}
                                        <li>
                                    )}
                                </ul>
                            <li>
                        )}
                    </ul>
                <li>
            )}
        </ul>
    );
}
Enter fullscreen mode Exit fullscreen mode

Of course, this is the short version of the frontend.

You can find the complete frontend code (with the amazing design) here:
https://github.com/novuhq/notification-directory-react

screenshot

I hope you have learned something. Until next time 😎

Help me out!

If you feel like this article helped you, I would be super happy if you could give us a star! And let me also know in the comments ❤️

https://github.com/novuhq/novu
Image description

Top comments (22)

Collapse
 
stepanzak profile image
Štěpán Žák

This is a really cool use for an AI.

Collapse
 
nevodavid profile image
Nevo David

Thank you Štěpán 🤩 how are you today?
Do you have any use for it?

Collapse
 
takmanjim profile image
takmanjim

Perfect thanks a lot

Collapse
 
nevodavid profile image
Nevo David

🎉
Hope you will find it a good use!

Collapse
 
nevodavid profile image
Nevo David

Have you used ChatGPT for coding before?

Collapse
 
nandansn profile image
Nandakumar R

yes

Collapse
 
nevodavid profile image
Nevo David

For what use case? :)

Thread Thread
 
nandansn profile image
Nandakumar R

i used for code refactoring

Collapse
 
ra1nbow1 profile image
Matvey Romanov

Wow! That's truly amazing

Collapse
 
nevodavid profile image
Nevo David

Thank you ❤️

Collapse
 
mtariqsajid profile image
Tariq Sajid

I am not able to us this package in Adonis js typescript. You mention that this library use esm and not common js how to import and use this in adonis js ?

Collapse
 
nevodavid profile image
Nevo David

You can do something like this:

import type { ChatGPTClient } from '@waylaidwanderer/chatgpt-api';
const { ChatGPTClient: chat } = await (eval(
      `import('@waylaidwanderer/chatgpt-api')`,
    ) as Promise<{
      default: typeof ChatGPTClient;
    }>);

    const lib = new chat(process.env.CHATGPT_API, {
      reverseProxyUrl: 'https://chatgpt.hato.ai/completions',
      modelOptions: {
        model: 'text-davinci-002-render',
      },
    });
Enter fullscreen mode Exit fullscreen mode

If you are using plain javascript, just remove the typing

Collapse
 
jjmpsp profile image
Joel Murphy

Just tried following this tutorial but it seems the method no longer works. See: github.com/waylaidwanderer/node-ch...

Collapse
 
nevodavid profile image
Nevo David

Thank you,
They will probably launch a new server soon :)

Collapse
 
mohammadparsajavidi profile image
mohammadparsa-javidi

Perfect 👏👌

Collapse
 
nevodavid profile image
Nevo David

Thank you 🙏🏻😊

Collapse
 
nevodavid profile image
Nevo David

Hi Friends!
If you want to know exactly when I post, just register to my newsletter here

Collapse
 
bakardev profile image
Muhib ur Rahman Bakar

Great. Thanks for posting.

Collapse
 
nevodavid profile image
Nevo David

Thank you for reading

Collapse
 
nevodavid profile image
Nevo David

How are you today? :)

Collapse
 
nayanika12 profile image
Nayanika-12

Image description

Could u please help me as to where shall I correct my code?

Some comments may only be visible to logged-in visitors. Sign in to view all comments.