export const meta = require('./metadata.json')
In the past years, GitHub became a technology mastodon. Since the acquisition by Microsoft, the company added a myriad of outstanding features. GitHub Actions, in-house CI/CD, a marketplace, the integration of npm, et al. In fact, GitHub is so powerful nowadays that it can be your all-in-all provider. You can use GitHub as a Service. GaaS, Gaas, Gaas!
GitHub's variety of products is extensive enough to power most of the web apps out there. Don't believe me? Let's go through my journey building Request For Maintainers (RFM).
This project is a community-driven platform to track any repository calling for support. From the product point of view, I want to help the community to have a clean record of the OSS status. On the tech side, this project is an experiment I got in my mind for so long. RFM runs 100% on GitHub services. And it's open-sourced so anyone is welcome to check the nuts and bolts.
If you want to build a web app that runs on GitHub you're gonna need some foundations. Basic things that are common to any web. But, before getting started, a disclaimer. I didn't read the terms and conditions and all the legal dullness. But I can sense this approach maybe exceeds the intended purposes.
-
Hosting: for what GitHub repositories excel. But wait, there's more! GitHub pages put your web under a CDN, with SSL certs, and all the other goodies you'll expect from your trusted provider.
-
Database: if you don't have anything to hide 🌝 then GitHub Issues is a wonderful database. For example, you can store a stringified JSON in the body. Or benefit from the Issue metadata.
-
Server: most of the heavy lifting will happen in the client. But sometimes you need some special functions to run on the cloud. Did anyone say serverless? 💊
-
Auth: Do you need to put some access restrictions? Well, GitHub has a top-notch OAuth service implemented.
-
Secrets: Another interesting side-effect is the env secrets management. Since GitHub repositories already have a secret values system. We can use that to run out GitHub Actions as serverless functions.
Let's say you have a basic ecommerce of only 3 digital products. Create a repository. Put your product data as issues. Code and deploy the static website.
Add some basic authentication and finally run the payment confirmation as a GitHub Action.
You can store confirmation tokens in another private repo since secrets are well kept in GitHub.
At this point, I guess you get the idea. But how we put together all the pieces? Let's see how I built RFM step by step. You'll see it's not that complex at all.
-
You need your web to be statically generated. There are tones of options:
Jekyll
,create-react-app
, an old-fashion rawHTML
. Whatever. The important bit is that to use GitHub pages it must be static. -
You consume your fancy database with the public GitHub API. The public part is a no key-required standard CRUD. Your web app will use this API to get GitHub Issues. You can tweak your query with multiple params: the state, labels, content, assignation, etc.
// request first 13 RFM open requests that matches "vapor"
const params = [
'repo:sospedra/rfm',
'state:open',
'label:search',
'vapor',
'in:title,body'
]
const root = 'https://api.github.com/search/issues'
const path = `${root}?q=${params.join('+')}&per_page=13`
const response = await fetch(path)
const payload: {
items: Items,
total_count: number
} = await response.json()
const items = payload.items.map<Request>((item) => {
// using the body as JSON with RFM data model
body: JSON.parse(item.body),
// benefit from the Issue metadata as well
createdAt: new Date(item.created_at),
title: item.title,
})
- When in need of server functions use GitHub Actions. These deliver an outstanding experience. They are so powerful. The basic idea is that a custom trigger will execute a custom function. There's a marketplace you can explore or you can build your own. It's free real state!
// rfm/.github/workflows/issue.yml
name: Create external issues
on:
# run when modify issue label
issues:
types: [labeled]
jobs:
create:
# filter only those issues with the label "search"
if: "contains(github.event.issue.labels.*.name, 'search')"
name: Create issue
runs-on: ubuntu-latest
steps:
# parse the body of the issue
- name: vars
id: vars
uses: gr2m/get-json-paths-action@v1.x
# create a new issue in the repo that needs support
- name: create-issue
# notice that logic operators are available
if: steps.vars.outputs.requestIssueFullName == 'NONE'
uses: maxkomarychev/oction-create-issue@v0.7.1
with:
token: ${{ secrets.RFM_BOT }}
title: 🚧 Is this repo looking for support?
owner: ${{ steps.vars.outputs.owner }}
repo: ${{ steps.vars.outputs.name }}
body: Heeenlo hoomans!
- To integrate RFM with GitHub I use the GitHub Query Parameters. You know, remember to avoid Hooman Paws™️ as much as possible. There's a well-known library that can do the heavy parsing for us: new-github-issue-url.
const createGithubIssue = (request?: SubmitRequest) => {
if (!request) return ''
return newGithubIssueUrl({
body: JSON.stringify(request, null, 4),
labels: ['search'],
repo: 'rfm',
title: request.fullName,
user: 'sospedra',
})
}
- Finally, tweak the limitations. You can either add some auth with the GitHub OAuth. Or play with the Issues metadata, Actions triggers, etc. In RFM the actions are executed only when the issue has a particular label. Only moderators can assign labels. That's the way I avoid RFM being a spammy tool.
Am I suggesting that y'all should use GaaS from now on? Well, absolutely no 🤷🏻♂️. But, there are some cases where this can be beneficial. I'm thinking of OSS projects, mostly. In fact, using GitHub Issues as blog post comments it's being a common practice for a long time. This is an evolution of the same concept. The actual point is: GitHub become an infra beast lately. We should keep an eye here and consider pushing the limits as much as possible.