#Go
#Templ
#HTMX

Delving into the GOTH Stack: My Experience Building a Blog with Go, Templ, and HTMX.

Goth Stack Image

Introduction

What is the GOTH Stack?

The GOTH Stack is a simple stack to create the most modern web applications.
I've used it to create GOTHStatic - My own blog from scratch.

I became aware of htmx.org beacuse of the great X memes.
The Primeagen created the beautiful name GOTH Stack for using Go + templ + HTMX.

Motivation

One of the primary motivations behind my decision to venture into the GOTH Stack, was a strong desire to learn something new and apply it in a real-world use case. While static site generators like Jekyll and Hugo are excellent choices for building blogs and websites, my goal went beyond just creating a blog — it was about expanding my skill set and exploring the capabilities of templ and HTMX.

I didn't necessarily need the extensive features and complexity of other tools at this stage. Instead, I wanted to start with something simpler that allowed me to grasp the fundamentals of templating and gradually build upon that foundation.

Essential Features for a Responsive and User-Friendly Blog

To start the project the right way I've wanted to point out some key features i want to accomplish in this project:

Markdown First!

I love the simplicity of Markdown, which is why I have been writing all my notes in Markdown for years. For this reason, I also wanted to write blog posts in Markdown.

1# Headline
2
3A paragraph describing something
4
5## Second Headline
6...

But Markdown doesn't look great in the Browser.
In the past, a time where i thought JavaScript is the answer for every frontend question, i would have used a JavaScript library to beautify the Markdown for the user. But why should I unnecessarily bloat the application with Javascript for the user interface? I need to convert Markdown to HTML in the backend to have a great performing website.

After a brief review, I decided to use goldmark as Markdown parser, which is extended by custom rules in parsing/transformer.go.

 1// Add Tailwindcss classes to rendered nodes
 2func (t *TailwindTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
 3	ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
 4		switch nt := n.(type) {
 5    //...
 6	case *ast.Paragraph:
 7		var buf bytes.Buffer
 8		buf.WriteString("text-lg leading-relaxed mb-4")
 9		nt.SetAttributeString("class", buf.Bytes())
10    //...
11 	return ast.WalkContinue, nil
12})

Of course, you could have made proper CSS definitions for the various elements, but that would have been boring.

Static File Generation with templ

After ensuring my Markdown get parsed to the correct HTML format, i needed to build templates to enhance the content with a navbar, footer and some smaller components.
To accomplish this task i've chosen templ.

.templ files will look like a mixture of HTML and Go.

 1package pages
 2
 3import "blog/web/components"
 4import "blog/web/shared"
 5import "blog/model"
 6
 7templ BlogPage(post model.Post, html string) {
 8	@shared.Root() {
 9		<div class="container mx-auto px-4">
10			<div class="md:flex">
11				<a href="/">
12					<button class="bg-steel-gray-700 hover:bg-steel-gray-800 rounded-lg px-6 py-2 flex">
13						<img src="/assets/back.svg" alt="back"/>
14						Return to Overview
15					</button>
16				</a>
17				<div class="md:ml-4 mt-2 md:mt-0 flex flex-wrap content-center">
18					for _, tag := range post.Tags {
19						@components.Tag(tag)
20					}
21				</div>
22			</div>
23			@components.Post(html)
24		</div>
25	}
26}

You can spot some go code in between of our template definition.
In line 18, a for loop is used to add each tag from post.Tags. That's the great benefit of using templ.

After running templ generate our post.templ file got converted to Go code => post_templ.go.
pages.BlogPage(post, "<h1>My raw HTML</h1>").Render(ctx, writer) writes the file with the given context to any writer: io.Writer.
In my case i've decided to write the content directly to a os.File, served directly in the /static folder.

After seeing the first results, i've seen the need for code highlighting. github.com/yuin/goldmark-highlighting/v2 does a beautiful job by using chroma to highlight codeblocks.

Upload of static files

Basic text and code highlighting was supported. I hadn't even thought about images. However, in order to create attractive posts, I absolutely need images.

An API could quickly remedy this. Images are now stored in the static/assets folder.

 1	api.POST("/asset", func(c *gin.Context) {
 2		form, _ := c.MultipartForm()
 3		files := form.File["files"]
 4		paths := []string{}
 5		for _, file := range files {
 6			path := fmt.Sprintf("assets/%s", file.Filename)
 7			c.SaveUploadedFile(file, "static/"+path)
 8			paths = append(paths, path)
 9		}
10		c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!\n%v", len(files), paths))
11	})

After uploading a resource to the API i can use the default ![myImage alt description](/assets/myImage) Markdwon definition.

I forgot to use HTMX

I've created a beautiful static site generator, turning http POST Requests with Markdown to HTML, but completly forgot that i want some API interactions with HTMX.
At this point i've had to find out which content i need to load dynamically. The whole content is static generated. I don't need any dynamic loading of content yet.
To provide a basic usage of HTMX i've decided to load the blog posts from a database. The HTML Pages are still static generated, but the list on the Landingpage is aggregated by an API call.

Adding a Database

On my workplace i mostly work with non-relational databases like MongoDB. To get back to the traditional SQL feeling i've chosen the most basic solution: SQLite.
SQLite is a preferable choice over managed database instances when you need a lightweight, embedded database solution directly integrated into your application, trading off advanced features for simplicity and reduced resource overhead.

The model.Post represents the currently saved data to a blog post.

 1package model
 2
 3type Post struct {
 4	ID          int
 5	Title       string
 6	Subtitle    string
 7	Description string
 8	Image       string
 9	Tags        []string
10	Slug        string
11}

Current usage of HTMX

Althrough the database is created now, i only use it to load the posts on the index.html. That's something i've also could render static if a blogpost is updated.
In the future i want to add some features, that will make the HTMX integration worth it:

Lessons Learned

The Future of GOTHStatic

The GOTHStatic project will still be the base of my blog for a while. I've had a lot of fun writing this project, and even more fun writing a blog post about the process.
As I continue to tinker and improve it, I'm excited to see where this journey takes me next. I appreciate you taking the time to follow my journey.

Follow me on 𝕏

GOTHStack on GitHub