DEV Community

Rishi Raj Jain
Rishi Raj Jain

Posted on

How to you use GitHub Issues as your CMS?

In this tutorial, I'll guide you through the process of setting up GitHub Issues as your data source / CMS with Astro Endpoints to handle data operations. I used this method when I launched an open-source LinkTree Alternative, itsmy.fyi. I'll break down the code into steps to make it easier to follow 👇🏻

Setup GitHub Issues Webhook

To set up GitHub Issues as your data source and establish a connection to GitHub, follow these steps:

Step 1: Create a GitHub Repository

If you don't already have a GitHub repository for your project, create one on GitHub.

Step 2: Generate a GitHub Personal Access Token

To interact with GitHub's API, you'll need a personal access token with the necessary permissions. Follow these steps to create one:

  • Go to your GitHub account settings.
  • In the left sidebar, click on Developer settings.
  • Click on Personal access tokens, and then Generate token.
  • Select the required scopes (typically, you'll need repo or specific repository access).

Generate the token and securely store it as GITHUB_API_TOKEN.

Step 3: Create a GitHub Repo Webhook

  • Under your repository name, click Settings. If you cannot see the Settings tab, select the dropdown menu, then click Settings.
  • In the left sidebar, click Webhooks.
  • Click Add webhook.
  • Under Payload URL, type the URL where you'd like to receive payloads at for example, https://itsmy.fyi/github/hook/issue.
  • Under Secret, type a string to use as a secret key. You should choose a random string of text with high entropy.

Store this secret as the environment variable named GITHUB_WEBHOOK_SECRET.

Step 4: Configure GitHub API Authentication

In your Astro project, create a configuration file (e.g., .env) to store the GitHub token and webhook secret securely. Make sure to use environment variables (i.e. via import.meta.env.ENV_NAME) to access the token in your code.

Enabling SSR in Your Astro Project

Modify your astro.config.mjs to have configuration as below (I used Vercel to deploy my Astro site) 👇🏻

// File: astro.config.mjs

import { defineConfig } from 'astro/config'
import vercel from '@astrojs/vercel/serverless'

export default defineConfig({
  // ...
  output: 'server',
  adapter: vercel(),
  // ...
})
Enter fullscreen mode Exit fullscreen mode

Setup Octokit

The octokit package integrates the three main Octokit libraries:

(1) API client (REST API requests, GraphQL API queries, Authentication)
(2) App client (GitHub App & installations, Webhooks, OAuth)
(3) Action client (Pre-authenticated API client for single repository)

Out of which, we're solely making use of (1) right now.

// API Reference
// https://github.com/octokit/rest.js

import { Octokit } from '@octokit/rest'

const auth = import.meta.env.GITHUB_API_TOKEN

export const octokit = new Octokit({ auth })
Enter fullscreen mode Exit fullscreen mode

Post Comments through Octokit

Using OctoKit createComment function, respond to events related to GitHub Issues upon executing relevant actions:

// API Reference
// https://github.com/octokit/rest.js

export const createComment = async (context, body) => {
  await octokit.rest.issues.createComment({
    owner: context.repository.owner.login,
    repo: context.repository.name,
    issue_number: context.issue.number,
    body,
  })
}
Enter fullscreen mode Exit fullscreen mode

Verify GitHub Request Signature

In the verifySignature function, you need to ensure that the incoming GitHub webhook request's signature matches the expected signature to verify the request's authenticity.

Here's a code snippet that demonstrates how you can verify the GitHub request signature:

import { createHmac } from 'crypto'

export const verifySignature = (context, header) => {
  const secret = import.meta.env.GITHUB_WEBHOOK_SECRET
  const expectedSignature = `sha256=${createHmac('sha256', secret).update(context).digest('hex')}`
  return header === expectedSignature
}
Enter fullscreen mode Exit fullscreen mode

Create GitHub Webhook Handler Endpoint

The POST function is the webhook handler that listens to GitHub events related to GitHub Issues. Depending on the event type (opened, edited, closed), you can perform different actions within this function.

Here's how you can handle the events:

// File: src/pages/github/hook/issue.ts

// Process rawBody from the request Object
async function getRawBody(request: Request) {
  let chunks = []
  let done = false
  const reader = request.body.getReader()
  while (!done) {
    const { value, done: isDone } = await reader.read()
    if (value) {
      chunks.push(value)
    }
    done = isDone
  }
  const bodyData = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0))
  let offset = 0
  for (const chunk of chunks) {
    bodyData.set(chunk, offset)
    offset += chunk.length
  }
  return Buffer.from(bodyData)
}

export async function POST({ request }) {
  // Parse the request body as JSON
  const rawBody = await getRawBody(request)
  // Convert the raw body to JSON
  const context = JSON.parse(rawBody.toString())

  // If not a suitable GitHub Issue event
  if (!['closed', 'edited', 'opened'].includes(context.action.toLowerCase())) {
    return new Response(null, 400)
  }

  // Verify the signature
  if (!verifySignature(rawBody, request.headers.get('X-Hub-Signature-256'))) {
    return new Response(null, 403)
  }

  if (context.action === 'opened') {
    // do action when a new issue is opened
    // await createComment(context, 'Thanks for creating a profile via itsmy.fyi')
    return new Response(null, { status: 200 })
  }

  else if (context.action === 'edited') {
    // do action when a new issue is edited
    // await createComment(context, 'Thanks for editing your profile via itsmy.fyi')
  }

  else if (context.action === 'closed') {
    // do actions when a new issue is closed
    // await createComment(context, 'Thanks for using itsmy.fyi')
    return new Response(null, { status: 200 })
  }
}
Enter fullscreen mode Exit fullscreen mode

You're Done!

In this tutorial, we've set up GitHub Issues as our data source, perform actions (such as creating comments) all automated via Astro endpoints.

Happy coding!

Top comments (0)