rewriting my blog in go

There's some special moments when for example a handful of successive nice events occur or planets mysteriously perfeclty align or, hey, a new terribly simple yet efficient techstack pops up.

That's it you've guessed it right, the last option it'll be.

Introducing the magical techstack :

To get a bit more familiar whit those technologies I thaught that it could be a good idea to rewrite (again) my blog using this stack..

Okay, Go is basically a better TypeScript

So, a while ago, I wrote on how I implemented my blog relying solely on the Github API to fetch articles and metadata associated with them, stored in a repo as .md files. It was done in Deno and the rendering was done through their framework, fresh. And then as a proof of concept I rewrote the whole thing using Bun in a 'server event driven' way, by mixing server side rendering of Lit WebComponents and the use of HTMX.

But you know, as a developer, you're always eager to learn and make new things right ?

So yeah, I had a pretty strong basis to itterate on for this new Go version of my blog.

The first goal was to port the Github API wrapper I wrote in TypeScript to Go, and that's on which topic this article will focus on (the whole templ + HTMX rendering part might come in the future).

My previous experiences with Go were ok, not great, just comme ci comme ça.

The main reason I had this opinion on Go was surely a lack of knowledge about the language in general.

After learning about its design patterns a bit more in depths and after having found an iterator module, I can now confidently say that I'm having a blast with this language. And to be honest, it feels like a superior version of TypeScript honestly.

Some part of the API Wrapper code are almost exactly the same (thanks to the iter module) and the magic of TS custom types that you can replicate in Go.

As an example here's how is done the getPostBySlug(slug) method

In Typescript :

 1public static async getPostBySlug(slug: string): Promise<IPost> {
 2    slug = `${slug}.md`
 3    const [commitForPost, contentForPost] = await Octoblog.getSeveral([
 4      {
 5        endpoint: ENDPOINTS.COMMIT,
 6        params: {slug}
 7      },
 8      {
 9        endpoint: ENDPOINTS.CONTENT,
10        params: {slug,ref:Bun.env.GITBRANCH}
11      },
12    ]);
13    return Octoblog.makePost((contentForPost as IContent), (commitForPost as Array<ICommit>), slug);
14  }

And in Go:

 1func (o *Octogo) GetPostBySlug(_slug string) *Post {
 2	slug := _slug + ".md"
 3	octoReqs := o.octoFetches(
 4		OctoReqs{
 5			One: &OctoRequest{
 6        verb:     "GET",
 7        endpoint: COMMIT_URL + _slug,
 8      },
 9			Two: &OctoRequest{
10        verb:     "GET",
11        endpoint: CONTENT_URL + _slug,
12      },
13		},
14	)
15	return o.newPost(Commits_B64{
16		One: parseResponseInto[[]CommitResponse](octoReqs.One),
17		Two: parseResponseInto[ContentResponse](octoReqs.Two).Content,
18	}, slug)
19}

We can see it's almost identical.

Same for when we have to map data, with the iter go module ...

 1public static async getAllPosts(): Promise<IPost[]> {
 2    const treeElements: TreeElement[] = (
 3      await Octoblog.get(ENDPOINTS.TREE)
 4    ).tree.filter(treeElement => 
 5      treeElement.path.includes('.md')
 6    );
 7    const path_sha = treeElements.map(post => [
 8      post.path, 
 9      post.sha
10    ]);
11    return (
12      await Promise.all(path_sha.map(tuple => 
13        Octoblog.getPost(tuple)
14      ))
15    ).sort((postA, postB) => 
16      postB.publishedAt - postA.publishedAt
17    );
18  }
 1func (o *Octogo) GetAllPosts() []*Post {
 2	treeElements := iter.Lift[TreeElement](
 3		parseResponseInto[TreeResponse](
 4      o.octoFetch(
 5        NewOctoReq(
 6          Req().Endpoint(TREE_URL)
 7        )
 8      )
 9    ).Tree).Filter(func(treeElement TreeElement) bool {
10		return strings.Contains(treeElement.Path, ".md")
11	})
12
13	slug_id := iter.Map[TreeElement, Slug_Id](
14      treeElements, func(treeElement TreeElement) Slug_Id {
15        return Slug_Id{
16          One: treeElement.Path,
17          Two: treeElement.Sha,
18        }
19	  },
20    )
21
22	iter.Map[Slug_Id, *Post](slug_id, o.getPost)
23
24	p := iter.Map[Slug_Id, *Post](slug_id, o.getPost).Collect()
25	By[Post](func(a, b *Post) bool {
26		return a.PublishedAt > b.PublishedAt
27	}).Sort(p)
28	return p
29}

One can argue that the Go version is slightly more verbose but that may be the cost of efficiency ?

So far I'm enjoying my time playing around with that iter module and trying out things with generics in Go.