How to Build Comment System for Gatsby Blog using GitHub Issues

How to Build Comment System for Gatsby Blog using GitHub Issues

Learn how to build a comment system for a Gatsby blog using GitHub Issues. Commenting systems enable you to interact with your blog readers.

Commenting systems enable you to interact with your blog readers. Here's how to build one for a Gatsby blog using GitHub Issues.

Comment systems play an important role because they enable you to interact with your blog readers. There are paid services such as Commento and Disqus available if you want to add a comment system to your blog, but in this article, you’ll learn how to build a comment system for a Gatsby blog using GitHub Issues.

Before jumping into the tutorial, let’s first see a demo and the workflow of our commenting system:

Demo workflow comment system Gatsby blog GitHub Issues

As you can see in this demo GIF, we’re able to comment in the blog. Our comments will be stored in GitHub Issues. Now, let’s see the workflow necessary to build something like this:

Workflow GitHub Authentication User Comments Issues

This workflow can be broken into three steps.

  1. When the user visits the blog, we check to see if the user is authenticated with GitHub. If so, the user can comment directly. If the user is not authenticated, then the user will need to sign in to comment on the blog.
  2. When a user clicks on the comment button, we also need to check whether an issue exists in the blog slug, title, or unique attribute of that blog. If an issue doesn’t exist, we’ll need to create a new issue.
  3. Finally, we publish the comment into GitHub Issues so it shows up in our blog comments.

Building GitHub authentication

First, we need to build GitHub authentication for our comment system. There are different ways to accomplish this, but instead of building it in Gatsby itself, we’ll use a custom server with Passport.js to handle authentication because it’s a simple and secure way to handle the user session.

Let’s create a custom server with Passport.js GitHub authentication.

npm init --yes
npm install express body-parser cors express-session passport passport-github

After that, create App.js and add the following code:

const express = require("express");
const bodyParser = require("body-parser");
const passport = require("passport");
const session = require("express-session");
const cors = require("cors");
const app = express();
const CLIENT_URL = process.env.CLIENT_URL || "http://localhost:8000";
app.use(cors({ credentials: true, origin: CLIENT_URL }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(
  session({
    resave: true,
    saveUninitialized: true,
    secret: "123456",
  })
);
app.use(passport.initialize());
app.use(passport.session());
require("./passport");
app.get("/", (req, res) => {
  console.log("user", req.user);
  res.send("Welcome");
});
app.get("/token", (req, res) => {
  if (req.user) {
    res.status(200).json({ user: req.user });
  } else {
    res.status(200).json({ user: null });
  }
});
app.get("/auth/github", (req, res, next) => {
  req.redirect_url = req.query.url;
  passport.authenticate("github", { state: JSON.stringify(req.query.url) })(
    req,
    res,
    next
  );
});
app.get(
  "/auth/github/callback",
  passport.authenticate("github", { failureRedirect: "/login" }),
  function (req, res) {
    const redirect_url = JSON.parse(req.query.state);
    // Successful authentication, redirect home.
    res.redirect(redirect_url);
  }
);
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
  console.log(`Server is running on PORT ${PORT}`);
});

Here, we have three endpoints: /auth/github, /auth/github/callback, and /token. They handle Passport.js GitHub authentication and Passport authentication callback and token, which returns the user information based on the cookies stored in the browser.

Next, create a file Passport.js to configure the passport.

const passport = require("passport");
const GitHubStrategy = require("passport-github").Strategy;
passport.use(
  new GitHubStrategy(
    {
      clientID: <YOUR APP CLIENT ID>,
      clientSecret: <YOUR APP CLIENT SECRET>,
      callbackURL: "http://localhost:4000/auth/github/callback",
      passReqToCallback: true,
      scope: ["public_repo", "repo"],
    },
    function (req, accessToken, refreshToken, profile, cb) {
      const user = {
        id: profile.id,
        name: profile.username,
        imageUrl: profile.photos[0].value,
        token: accessToken,
      };
      return cb(null, user);
    }
  )
);
passport.serializeUser(function (user, fn) {
  fn(null, user);
});
passport.deserializeUser(function (user, fn) {
  fn(null, user);
});

We now need client ID and client secret for the configuration.

To do this, go to GitHub settings, then click developer settings:client ID GitHub authentication

Next, click on OAuth apps:OAuth apps GitHub Issues Authentication

Finally, click on new OAuth app:OAuth App GitHub Issues Authentication

After this, you can create a new app and get credentials for it. We now have the server for your comment system, so let’s build a comments section inside a Gatsby blog. I’m going to use Gatsby Starter Blog to build the comments system on top of it.

Here’s the technical workflow inside the Gatsby comments system:

Gatsby starter blog comments system workflow

First, we have a New Comment component that posts the comment to GitHub Issues. Then, we have Comment, which renders each component from GitHub Issues. Both New Comment and Comment should be inside templates/blog-post.js, which renders each blog.

Next, create a component inside src directory Comment/newComment.js and add the following code:

import React, { useState, useEffect } from "react"
import { getLoginUrl } from "../../utils/auth"
import { renderMarkdown, processRenderedMarkdown } from "../../utils/github"
import avatar from "./avatar.svg"
import ReactMarkdown from "react-markdown"

const NewComment = ({ user, pageUrl, onCommentSubmit }) => {
  const [comment, setComment] = useState("")
  const [activeTab, setActiveTab] = useState(0)
  const [markDown, setMarkDown] = useState(null)
  const onCommentChange = e => {
    setComment(e.target.value)
  }
  const onPreviewClick = async () => {
    setActiveTab(1)
    const markdownComment = await renderMarkdown(comment)
    setMarkDown(markdownComment)
  }
  const onNewCommentSubmit = () => {
    onCommentSubmit(comment)
    setComment("")
  }
  return (
    <article className="timeline-comment">
      {user ? (
        <a
          className="avatar"
          target="_blank"
          tabindex="-1"
          href={user.html_url}
        >
          <img height="44" width="44" alt="@ganeshmani" src={user.avatar_url} />
        </a>
      ) : (
        <img height="44" width="44" src={avatar} />
      )}
      <form className="comment" acceptCharset="UTF-8" action="javascript:">
        <header className="new-comment-header tabnav">
          <div className="tabnav-tabs" role="tablist">
            <button
              type="button"
              className="tabnav-tab tab-write"
              role="tab"
              onClick={() => setActiveTab(0)}
              aria-selected={activeTab === 0}
            >
              Write
            </button>
            <button
              type="button"
              className="tabnav-tab tab-preview"
              role="tab"
              onClick={() => onPreviewClick()}
              aria-selected={activeTab === 1}
            >
              Preview
            </button>
          </div>
        </header>
        <div className="comment-body">
          <textarea
            className="form-control"
            placeholder="Leave a comment"
            aria-label="comment"
            value={comment}
            onChange={onCommentChange}
            disabled={!user}
          >
            {activeTab === 1 && markDown ? markDown : null}
          </textarea>
          <div className="markdown-body" style={{ display: "none" }}></div>
        </div>
        <footer className="new-comment-footer">
          <a
            className="text-link markdown-info"
            tabindex="-1"
            target="_blank"
            href="https://guides.github.com/features/mastering-markdown/"
          >
            <svg
              className="octicon v-align-bottom"
              viewBox="0 0 16 16"
              version="1.1"
              width="16"
              height="16"
              aria-hidden="true"
            >
              <path
                fill-rule="evenodd"
                d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15
              13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4
              8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"
              ></path>
            </svg>
            Styling with Markdown is supported
          </a>
          {user ? (
            <button
              onClick={e => onNewCommentSubmit()}
              className="btn btn-primary"
              type="submit"
            >
              Comment
            </button>
          ) : (
            <a
              className="btn btn-primary"
              href={`${getLoginUrl(pageUrl)}`}
              target="_top"
              hidden=""
            >
              Sign in to comment
            </a>
          )}
        </footer>
      </form>
    </article>
  )
}
export default NewComment

Let’s start from component props. Here, we have user, pageUrl, and onCommentSubmit. We’ll first check to see if the user is authenticated or not based on the user props.

gatsby web-development react javascript developer

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

How native is React Native? | React Native vs Native App Development

Article covers: How native is react native?, React Native vs (Ionic, Cordova), Similarities and difference between React Native and Native App Development.

Hire Dedicated React Native Developer

Have you ever thought of having your own app that runs smoothly over multiple platforms? React Native is an open-source cross-platform mobile application framework which is a great option to create mobile apps for both Android and iOS. **[Hire...

Hire Dedicated React Native Developer in India | React Native Development

Hire dedicated React Native developers for your next project. As the top react native development company we offer you the best service as per your business needs.

Hire React Js Developer from Expert React JS Development Company

Are you looking to [hire React JS developer](https://www.mobiwebtech.com/react-js-development-company/ "hire React JS developer") from a prestigious and reliable React JS development company? Visit at Mobiweb Technologies here we have a big team...

Why Web Development is Important for your Business

With the rapid development in technology, the old ways to do business have changed completely. A lot more advanced and developed ways are ...