How to Build a Guestbook Feature with Firestore and Perspective API (Part 2)

How to Build a Guestbook Feature with Firestore and Perspective API (Part 2)

Personalize your Next.js website with a guestbook feature! Part 2: Perspective API implementation

Welcome back! In this article, I'll guide you through a step-by-step of building a Guestbook feature for your Next.js website.

In the previous article, we have covered the following:

  • Set up Next.js website

  • Set up Firestore database in Firebase

  • Set up GitHub OAuth authentication

  • Implementation of fetching, displaying and posting comments

  • Implementation of authentication

Let us continue where we left off and start implementing Perspective API to moderate content for our guestbook.

What is Perspective API?

The Perspective API is a machine learning model developed by Jigsaw, a subsidiary of Alphabet Inc. (Google's parent company). The goal of the Perspective API is to help identify and filter out toxic comments and online harassment in user-generated content. It uses natural language processing (NLP) techniques to analyze text and assess the potential for toxicity, which includes content that may be offensive, disrespectful, spam or harassing.

The best part about the Perspective API is that it is completely free! As developers, we can integration this API into our systems to automatically moderate and filter user-generated content.

For our guestbook feature on our website, we are using Perspective API to determine the toxicity score when anyone signs on our guestbook. In the case someone writes an offensive or spam comment, we can block the content from being uploaded to our Firestore database.

Step 1: Obtain Perspective API credentials

Go to Google Cloud console and use an existing project or create a new one. You should have a Project ID.

Once you have a Google Cloud account and project set up, please fill out this form to gain access to the API. You will need to include the Project ID in the form.

In about 24-48 hours, you should receive an email that notifies you can enable the API. They will provide a link where you can follow the instructions to enable the API.

Now we can go to our Google Cloud project and enable the Perspective Comment Analyzer API and click "Create Credentials" to obtain the API credentials for Perspective API.

Step 2: Create perspectiveApi.js

First, install the npm package perspective-api-client as this will make it easier to implement the API into our website.

npm install perspective-api-client

In your project, create a perspectiveApi.js file. This is where we will import our API credentials and use the API. Remember to add your API key in your .env file.

//services/perspectiveApi.js
const Perspective = require('perspective-api-client');
const perspective = new Perspective({apiKey: process.env.PERSPECTIVE_API_KEY});

Next, let us create a function that will receive the text content of the comment added by a user on the guestbook. This function will analyze the text for the attributes we want to moderate on: toxicity, spam and insult. Below is how we will write the function.

//services/perspectiveApi.js
export default async function analyzeText(text) {
  try {
    const result = await perspective.analyze(
      text,
      {attributes: ['toxicity', 'spam', 'insult']}
    );

    return result;
  } catch (error) {
    console.log('Error analyzing text with Perspective API:', error);
    throw error;
  }
}

Step 3: Call analyzeText

In our Login component, where our handleComment function is, let's call analyzeText to check whether the comment posted should be stored in our Firestore database or not. If Perspective API analysed the text as toxic, spammy or insulting, we will not upload the comment to Firestore and will not display the comment.

//components/Login.js

import { useSession, signIn, signOut } from "next-auth/react";
import { Button } from './custom-button';
import analyzeText from '../services/perspectiveApi'; // add this line
import { useState } from 'react';

const LoginButton = ({ onCommentSubmit }) => {
  const { data: session } = useSession();
  const [comment, setComment] = useState('');
  const [loading, setLoading] = useState(false);

  const fetchPerspectiveAnalysis = async (textToAnalyze) => {
    try {
      const result = await analyzeText(textToAnalyze);
      console.log(result);
      return result;
    } catch (err) {
      console.error(err)
    }
  };

  const handleWriteComment = async (e) => {
    e.preventDefault();

   // analyze text before uploading
   let perspectiveResult = await fetchPerspectiveAnalysis(comment);
   if (!perspectiveResult || perspectiveResult?.attributeScores?.TOXICITY.summaryScore.value > 0.6 || perspectiveResult?.attributeScores?.INSULT.summaryScore.value > 0.6 || perspectiveResult?.attributeScores?.SPAM.summaryScore.value > 0.3){
     window.alert("Our AI detected toxic/spam content. Please write something nicer!");
      return;
   }

   setLoading(true);
   try {
      const db = getFirestore(firebaseApp);
      const commentRef = collection(db, "comments");
      await addDoc(commentRef, {
        email: session?.user?.email,
        name: session?.user?.name,
        image: session?.user?.image,
        comment: comment
      })
      onCommentSubmit({
          email: session?.user?.email,
          name: session?.user?.name,
          image: session?.user?.image,
          comment: comment
        });

      window.alert("Comment submitted!")
      setComment('');
    } catch (error) {
      console.error("Error submitting: " + error)
    }
  };

  if (session) {
    return (
      <>
        <form onSubmit={handleWriteComment}>
          <textarea
            disabled={loading}
            placeholder="Write anything here ✍️"
            rows={4}
            cols={50}
            value={comment}
            onChange={(e) => setComment(e.target.value)}
          ></textarea>
          <p>Sincerely from {session?.user?.name}</p>
          <div className="mt-2 flex items-center justify-between">
            <Button label={loading ? 'Submitting...' : 'Submit'} />
            <Button onClick={() => signOut()} type="outline" buttonType="button" label="Sign out" />
          </div>
        </form>
      </>
    );
  }

  return (
    <>
      <Button onClick={() => signIn()} type="primary" label="Sign my guestbook!" />
    </>
  );
};

export default LoginButton;

Demo!

Below shows the demo of the completed guestbook feature! As you can see, any spam comments are blocked from being uploaded to Firestone.

Conclusion

And that wraps up the 2-part series on how to add a guestbook feature with Firestore and Perspective API! I hope this has been a fun project you can follow along to try building. I do recommend trying to customize with more features as a challenge like:

  • Add a rate limiter

  • Adjust the threshold of toxicity, spam or insult to your liking

  • Implement Perspective API and Firestore on server instead

If you have any thoughts or comments you would like to share, do leave a comment below. And if you find this article helpful, do leave a like and share. Cheers!

Did you find this article valuable?

Support Victoria Lo by becoming a sponsor. Any amount is appreciated!