When i first thought about having a personal blog, i started wondering how i wanted my authoring experience to be like, and although there are plenty of great tools, i still wanted to find something smart and creative.
I found out that GitHub discussions are really powerful and their API gives us (almost) all the right tools to create a complete blog engine. In fact this very own blog is completely powered by GitHub discussions!
🔍 Initial research
After evaluating a few blog solutions, i thought: "I'll just use GitHub and version my posts as Markdown files".
The authoring experience is pretty straightforward: create a Markdown file in your repository and push it. You can even create files directly from the GitHub UI, and you also get a nice Markdown preview.
Although this method works, i still wanted something better.
Versioning Markdown files on a repository didn't feel cool to me, but GitHub still has a few alternatives that kept me interested.
At first, i thought about using issues as blog posts: they have a title, a Markdown editor, labels (which are tags in every aspect ), comments, reactions, and a good search API.
Unfortunately, issues miss something critical for a blog engine: everyone can create an issue on a public repo. 😱
After some research, i wasn't able to find a way around this problem without involving complex actions and hooks, so i looked at a similar alternative: discussions.
✅ The right tool for the wrong job
GitHub discussions share all the cool things that issues bring, plus a few more. Lets do a quick recap:
- A title
- a Markdown editor with preview
- labels (tags)
- comments
- reactions
- powerful search
- writing permissions
- categories
- upvotes
- pinning
- subscribe for updates
- decent mobile authoring experience with the GitHub mobile app
- infinite integrations through actions
After a bit of tinkering i was able to create a simple blog engine by following these simple steps:
1️⃣ Edit discussion categories
Discussion categories are important for our goal:
- They are mandatory, so you need at least one, even if you don't plan on using categories in your blog;
- You need to edit for each category their "Discussion format" and select "Announcement".
When setting a category as announcement, only maintainers of the repository can create discussions in that category, and if all your categories are set as announcement, the "New discussion" button will disappear for everyone besides maintainers.
2️⃣ Create our first post
To query our post we have to use its internal id, which is not visible through the default GitHub UI, and would also give us a non-pleasant URL format, ie: /blog/D_kwDOGN2Rdc4AN6ar
.
To use pretty URLs we have to be a bit creative: the method i chose is to add a frontmatter section in the body of my posts. GitHub Markdown preview does not complain about it, so it's an effective way to allow us to add metadata to our posts.
Using the frontmatter section, we can add a slug
property that will serve as the search term to fetch our post. (I've also added another frontmatter property that serves as a short description of the post)
Note that the uniqueness of the slug is entirely up to us. As long as our blog does not have hundreds of posts it won't be a problem.
This is what our first post looks like in Markdown:
---
slug: our-first-blog-post
description: Writing our first blog post as a GitHub discussion
---
## This is a post
How cool is that?
3️⃣ Query our post using the GitHub API
We can query our post with a simple Graphql query (REST API is not available for new GitHub features like discussions):
query ($query: String!) {
search(query: $query, type: DISCUSSION, first: 1) {
edges {
node {
... on Discussion {
title
body
createdAt
updatedAt
category {
id
name
}
labels(first: 10) {
edges {
node {
id
name
color
}
}
}
}
}
}
}
}
Our query variable will contain the following string:
const repo = 'reegodev/reego.dev'
const slug = 'our-first-blog-post'
const variables = {
query: `"slug: ${slug}" in:body repo:${repo}`
}
This query basically searches the first discussion that contains the slug of our post in the body and is included in a specific repo. After running this query, we should be able to retrieve our post and render it using our favorite Markdown parser.
Don't forget to create a personal access token with all the "repo" permissions to send along as a bearer token
4️⃣ Embed discussion comments ( optional )
To embed discussion comments directly into our articles i've found an amazing project: Giscus. It fetches comments using the GitHub API, and lets your visitors post directly from your website with their GitHub account. Their website allows you to create the Giscus configuration by answering a few questions, mine ended up being this:
<script src="https://giscus.app/client.js"
data-repo="reegodev/reego.dev"
data-repo-id="R_kgDOGTk7pw"
data-mapping="number"
data-term="{ post.number }"
data-reactions-enabled="1"
data-emit-metadata="0"
data-theme="dark_dimmed"
data-lang="en"
crossorigin="anonymous"
async>
</script>
After embedding this little snippet, comments will just show up, it's that easy!
🚀 A great potential?
Fetching a single post only scratches the surface of what we can achieve using discussions as our blog engine. There are many things that we can do with the GitHub API which i have not covered in this post:
- Fetching the list of posts ordered by creation date to populate our blog index
- Fetching pinned posts to show at the top of our blog index or homepage
- Fetching posts by tag to provide tag archives
If you are interested in digging deeper into other functionalities you can browse the source code of my blog here