{"id":13479613,"url":"https://github.com/renatorib/github-blog","last_synced_at":"2025-04-05T07:07:06.070Z","repository":{"id":46776549,"uuid":"354366448","full_name":"renatorib/github-blog","owner":"renatorib","description":":octopus: Turn your github issues into a CMS for your blog.","archived":false,"fork":false,"pushed_at":"2023-06-08T23:28:52.000Z","size":849,"stargazers_count":395,"open_issues_count":0,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-04-14T06:36:13.355Z","etag":null,"topics":["blog","cms","github","issues"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/renatorib.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-03T18:33:55.000Z","updated_at":"2024-06-14T00:45:37.663Z","dependencies_parsed_at":"2024-06-14T00:45:27.393Z","dependency_job_id":"23e9b30f-d816-413a-9404-0009840b1333","html_url":"https://github.com/renatorib/github-blog","commit_stats":{"total_commits":21,"total_committers":1,"mean_commits":21.0,"dds":0.0,"last_synced_commit":"dcc638e7ea3f61e8a3b3fd0b4b9b136054ca69d1"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renatorib%2Fgithub-blog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renatorib%2Fgithub-blog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renatorib%2Fgithub-blog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renatorib%2Fgithub-blog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/renatorib","download_url":"https://codeload.github.com/renatorib/github-blog/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247299832,"owners_count":20916190,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blog","cms","github","issues"],"created_at":"2024-07-31T16:02:20.122Z","updated_at":"2025-04-05T07:07:06.059Z","avatar_url":"https://github.com/renatorib.png","language":"TypeScript","readme":"# Github Blog\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"cover.png\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  Turn your github issues into a CMS for your blog.\n\u003c/p\u003e\n\n```sh\nnpm install @rena.to/github-blog\n```\n\n## API Only\n\n**This repository is just about the API.**\n\nNote:\n\n\u003e If you're looking for something more 'high level', like a full-featured blog application, I'm working on a starter template using Next.js, TypeScript and Tailwindcss. [Follow me on twitter](https://twitter.com/renatorib_) to follow up and receive updates.\n\n## Concept\n\nThe main idea is simple: each issue is a blog post entity.\n\nTaxonomy is managed by **labels** and have `\u003ckey\u003e:\u003cvalue\u003e` structure. Like `type:post`, `tag:javascript`, etc. Labels can be used to filter posts on querying, but is also available on post too. So you can use to add any kind of flags to your post.\n\nThe built-in label keys are: `type`, `state`, `tag`, `flag` and `slug`.\n\n- Use **type** labels to differentiate _post_ from _article_, for example.\n- Use **state** labels to handle _published_ and _draft_.\n- Use **tag** labels to add tags to your posts, like _typescript_.\n- Use **flag** labels to add any kind of flag to your post, like _outdated_ to mark post as outdated.\n- Use **slug** label to define an slug to your post. [Read about slug problem](#slug-problem).\n\nYou can also add any **k:v** labels to your post, like `foo:bar`.\n\n## Table of Contents\n\n- [Getting Started](#getting-started)\n  - [Repository](#repository)\n  - [Issue](#issue)\n  - [Fetch](#fetch)\n- [Guide](#guide)\n  - [Querying](#querying)\n  - [Searching](#searching)\n  - [Sorting](#sorting)\n  - [Pagination](#pagination)\n  - [Defaults](#defaults)\n  - [Comments](#comments)\n- [Problems](#problems)\n  - [Slug Problem](#slug-problem)\n- [API Reference](#api-reference)\n\n## Getting Started\n\nLet's create your first blog post.  \nYou will need: 1) a repository, 2) an issue with some labels\n\n#### Repository\n\nFirst, you will need to create a repository to publish your posts.\n\nIt can be private, but I recommend you to create a public since it will allow people comment and react to your posts.  \nRandom people will be able to create issues but they can't add labels. So you can control what posts will be shown using some label like `type:post` for example. It will prevent random people to post on your blog. Also, by core github-blog only fetches by opened issues. You can close any random issue opened by others to keep posts organized.\n\n![image](https://user-images.githubusercontent.com/3277185/115134566-a8039180-9fe7-11eb-9e74-eb23b488e860.png)\n\n#### Issue\n\nCreate a issue with your content and add the labels `state:published`, `type:post`.  \nAlso add an label to your slug like `slug:my-first-post`.\n\n\u003e Tip: Your issue content can have frontmatter data\n\n![image](https://user-images.githubusercontent.com/3277185/115800402-ec5cac00-a3b0-11eb-9523-49dbaa341354.png)\n\n#### Fetch\n\nHere comes github-blog. First install\n\n```sh\nnpm install @rena.to/github-blog\n```\n\nNow create a new blog instance passing your repo and your github token.  \n[Create your token here ⟶](https://github.com/settings/tokens).\n\n```ts\nimport { value GithubBlog } from \"@rena.to/github-blog\";\n\nconst blog = new GithubBlog({\n  repo: \"\u003cuser\u003e/\u003crepo\u003e\", // e.g.: \"renatorib/posts\"\n  token: \"\u003ctoken\u003e\",\n});\n```\n\nFetch your post using getPost:\n\n```ts\nconst post = await blog.getPost({\n  query: { slug: \"my-first-post\" },\n});\n```\n\nFetch post comments using getComments:\n\n```ts\nconst comments = await blog.getComments({\n  query: { slug: \"my-first-post\" },\n  pager: { first: 100 },\n});\n```\n\nFetch all your posts using getPosts:\n\n```ts\nconst posts = await blog.getPosts({\n  query: { type: \"post\", state: \"published\" },\n  pager: { limit: 10, offset: 0 },\n});\n```\n\nThat's all.\n\n## Guides\n\n### Querying\n\nAll query works by AND logic. You can't query by OR because of the nature and limitations of github search.  \nBut you can exclude results using prefix `not` (`notType`, `notState`, etc.)  \nE.g: If you want to query posts with type _post_ but it can't have a flag _outdated_, you can use:\n\n```ts\nconst posts = await blog.getPosts({\n  query: { type: \"post\", notFlag: \"outdated\" },\n  pager: { limit: 10, offset: 0 },\n});\n```\n\nYou can also pass an array to most of query params:\n\n```ts\nconst posts = await blog.getPosts({\n  query: { type: [\"post\", \"article\"], tag: [\"javascript\", \"react\"] },\n  pager: { limit: 10, offset: 0 },\n});\n```\n\n### Searching\n\nYou can also search for post that contain terms using `query.search` param:\n\n```ts\nconst posts = await blog.getPosts({\n  query: { type: \"post\", state: \"published\", search: \"compiler\" },\n  pager: { limit: 10, offset: 0 },\n});\n```\n\n### Sorting\n\nYou can sort results by `interactions`, `reactions`, `author-date`, `created`, `updated`.  \nAll of them are desc by default but you can suffix with `-asc`. See all [in docs](/docs)\n\n```ts\nconst posts = await blog.getPosts({\n  query: { type: \"post\", sort: \"interactions\" },\n  pager: { limit: 10, offset: 0 },\n});\n```\n\n### Pagination\n\nYou can paginate using `pager.limit` and `pager.offset` as you saw before, but you can also paginate using cursors with the pager params `after`, `before`, `first` and `last`.\n\n```ts\n// first 10 posts\nconst posts = await blog.getPosts({\n  query: { type: \"post\" },\n  pager: { first: 10 },\n});\n\n// more 10 posts\nconst morePosts = await blog.getPosts({\n  query: { type: \"post\" },\n  pager: { first: 10, after: posts.pageInfo.endCursor },\n});\n```\n\n\u003e **NOTE:** `limit` and `offset` uses `first` and `after` under the hood.  \n\u003e So if you pass both `limit` and `first` or `offset` and `after`, limit and offset will be ignored.\n\n### Defaults\n\nYou can set some defaults for querying right in your blog instance, if you want to avoid some query repetition:\n\n```ts\nconst blog = new GithubBlog({\n  repo: \"renatorib/posts\",\n  token: process.env.GITHUB_TOKEN,\n  queryDefaults: {\n    state: \"published\",\n    type: \"post\",\n  },\n});\n\nconst posts = await blog.getPosts({\n  pager: { first: 10, offset: 0 },\n});\n```\n\n### Comments\n\nYou can fetch all post comments using `getComments` method\n\n```ts\n// first 10 comments\nconst comments = await blog.getComments({\n  query: { slug: \"my-first-post\" },\n  pager: { first: 10 },\n});\n\n// more 10 posts\nconst moreComments = await blog.getComments({\n  query: { slug: \"my-first-post\" },\n  pager: { first: 10, after: comments.pageInfo.endCursor },\n});\n```\n\n\u003e **NOTE:** Comment pagination by _limit_ and _offset_ is still not possible while I figure out on how generate v2 cursors based on offset.  \n\u003e Read more about this issue here, maybe you can help.\n\n## Problems\n\nGithub issues and Github API of course isn't designed to this kind of usage. So I ended up bumping into some limitations during the design and construction of the project. Here I list some of them and try to describe the problem and how I tried to get around.\n\n### Slug Problem\n\nOne of my biggest disappointments. It's impossible to create a safe and unique slug for your posts.\n\nMy first attempt was to use issue title to slug, and define the actual post title into issue's frontmatter.  \nBut it does not worked because:\n\nGithub only let you query for an exact repo/issue using the number of it, and I don't want to put id/number into my urls.\n\n```graphql\nquery {\n  repository(owner: \"renatorib\", name: \"posts\") {\n    issue(number: 1) { // get issue at https://github.com/renatorib/posts/issue/1\n      title\n    }\n  }\n}\n```\n\nGithub repository issues only allow you to filter using labels, states (closed/open), assignee, dates, etc. Nothing that let me use the title.\n\n```graphql\nquery {\n  repository(owner: \"renatorib\", name: \"posts\") {\n    issues(...filters) {  // some specific filters, nothing useful\n      title\n    }\n  }\n}\n```\n\nSo I was forced to use the [query search](https://docs.github.com/en/github/searching-for-information-on-github/getting-started-with-searching-on-github/understanding-the-search-syntax) that I find more powerful and I could filter by `repo:owner/name`\nNow I can find the issue using title this way:\n\n```graphql\nquery {\n  search(type: ISSUE, first: 1, query: \"repo:renatorib/posts slug-name\") {\n    nodes {\n      ... on Issue {\n        title\n      }\n    }\n  }\n}\n```\n\nBut it isn't _reliable_. I can't search for an _exact_ title with query search and it could return an issue with title of `slug-name-foo` instead of the `slug-name` depending on the sort rules.\n\nI gave up and ended using labels for that. Now I can query by exact slug:\n\n```graphql\nquery {\n  search(type: ISSUE, first: 1, query: \"repo:renatorib/posts label:slug:slug-name\") {\n    nodes {\n      ... on Issue {\n        title\n      }\n    }\n  }\n}\n```\n\nIt works. But the problem is that it isn't the ideal. Each post is a new label, it don't scale well.\n\n### Pagination by limit/offset problem\n\nTODO\n\n## API Reference\n\nSee at [/docs](/docs) (auto-generated from typescript types)\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frenatorib%2Fgithub-blog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frenatorib%2Fgithub-blog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frenatorib%2Fgithub-blog/lists"}