I'm a fickle soul
How I've gone and changed the stack again
10 months ago
Intro
So yes I was having ALL the fun doing front end code with Typescript and SvelteKit but I have decided to try and play with something that is the complete 180 from not only SPA and Client / Server frontends but a solution where I write minimal JavaScript.
GOing with the GoTH stack (Go, Templ and HTMX
) It's a 100% Go solution for not only your backend REST code but also for the creation of your HTML templates / components using the Templ framework. Interactivity is provided from HTMX which is a HATEOAS (Hypermedia as the engine of application state) framework where you provide Server-side rendered html direct to the browser and HTMX can do dynamic replacement of the DOM and other stuff with just some HTMX attributes on your HTML elements - Use JavaScript so I write less JavaScript.
GoTH Stack Overview The GoTH stack revolves around three core components:
Go serves as the backbone of the stack, offering me type safety, performance, and familiarity, given my years with the language. Its versatility makes it a natural choice for backend development tasks.
Templ is the templating framework of choice, mainly due to the type safety and efficiency when generating HTML templates. Templ’s integration which uses code generation with the templ file definitions it does make templating much better than the classic go templates.
HTMX represents the bridge between traditional server-rendered HTML and dynamic client-side interactions. HTMX enables the manipulation of DOM elements with its own html tags which allows the SPA features without the complexities of SPAs.
So with this stack I found myself super productive, much more productive than I was in the JavaScript world but that’s to be expected considering Go would be my language of choice in most cases and the one I have the most experience in.
Backend
Standard stuff here, I decided to go with the Echo HTTP Framework, I’ve used it a good bit in the past and am comfortable, and to be honest I prefer the handler semantics with the fact that the handler functions return an error. So as per my usual layout we have our API layer structs usually postfixed with Request then we have our business logic representation and finally the Database representation, usually postfixed with Row. This stop the front end being coupled to your database row e.g.
ArticleRequest -> Article -> ArticleRow
I follow a standardised approach to creating my REST handlers, for each endpoint type I have a handler struct that can contain all other dependencies it requires to make those calls. Each handler has a RegisterRoutes
function to create the endpoints.
Here is my static handler that mounts my compiled assets, tailwind CSS and some images mainly.
REST Handlers
type StaticHandler struct {
}
func NewStaticHandler() *StaticHandler {
return &StaticHandler{}
}
func (h *StaticHandler) RegisterRoutes(router *echo.Echo) {
router.Static("/static", "assets")
}
Here is a more traditional handler with a Repository field.
type Article struct {
Title string
Subtitle string
Body string
Image string
}
type ArticleHandler struct {
Repo *repo.ArticleRepository
}
func NewArticleHandler(config repo.Config) (*ArticleHandler, error) {
ar, err := repo.NewArticleRepository(config)
if err != nil {
return nil, err
}
return &ArticleHandler{
Repo: ar,
}, nil
}
func (h *ArticleHandler) RegisterRoutes(router *echo.Echo) {
router.GET("/articles", h.ListArticles)
router.GET("/articles/:slug", h.Article)
}
func (h *ArticleHandler) ListArticles(c echo.Context) error {
articleRows, err := h.Repo.GetArticles()
if err != nil {
return err
}
articles := make([]article.Article, len(articleRows))
for pos, a := range articleRows {
articles[pos] = a.ToArticle()
}
return render(c, article.ListArticles(articles))
}
func (h *ArticleHandler) Article(c echo.Context) error {
slug := c.Param("slug")
articleRow, err := h.Repo.GetArticleBySlug(slug)
if err != nil {
return err
}
a := articleRow.ToArticle()
clapped := false
cookies := c.Cookies()
for _, cookie := range cookies {
if cookie.Name == clapID(a.ID) {
clapped = true
}
}
return render(c, article.Show(a, clapped))
}
Here is the articles handler for showing the list of Articles and the individual Article, as you can see I have defined my own Article
struct that is provided to the template, this way I do not have tight coupling with the database entity which in this case is an ArticleRow
type.
Repository
As usual I find the repository pattern useful for laying out the interactions with the database. I will also always return a specific ROW Type entity that has the sql
package typed fields so that you can do NULL
checks etc. with types from the database. It's best practice to do this as close to the database interactions as possible with the ArticleRow
type, you could also COALESE in your SQL statement so all null-able fields are defined a default return for the NULL
scenario.
For the actual Database I’m just using a SQLite database with segregated connections. As SQLite is single threaded the read connection is still available when the write connection is busy when the locks are on other tables, not like this is going to come into play as I’m not writing blog posts all day.
Templates
As is the way with these things the hotness at the moment is Templ. Its a fully go typed templating framework that allows the type safety that the native go templates do not have. This has been very nice to work with and is definitely a step up from the native templating engine. Templ using a generation step to take the .templ definitions and then produce the actual go code. What’s nice is there is a LSP for VSCode for Templ for code completion etc.
HTMX
So the main thing here that I wanted to play with was the HTMX library - it’s been getting much more popular recently and promises to be a low javascript way to get a lot of the benefits of a SPA by using the existing HTML syntax and the special HTMX tags. There is a few tags so it might take a little while to get the surface area covered but for general run of the mill type problems its been a god send. Few HTMX tags on your exiting HTML and you are away, then with the Templ framework it is a nice quick feedback loop. The main use I had for HTMX was to add the ‘Clap’ feature to allow some sort of way to hot-swap the elements and give a better user experience. The strangest thing probably is the building out of the HTML API endpoints for the direct HTML but once going its a nice flow to it. Using the htmxpost
for the wrapper for AJAX calls for the too and fro from the server side. This was great for adding the ‘Claps’ feedback feature on the bottom of all the articles.
Here is the GoComponent:
package claps
import (
"strconv"
)
func clapURL(id int) string {
return "/claps/" + strconv.Itoa(id)
}
func clapCountURL(id int) string {
return "/claps/" + strconv.Itoa(id) + "/count"
}
func countString(count int) string {
if count == 0 {
return ""
}
return strconv.Itoa(count)
}
templ ClapHeader(id, count int) {
<div class="clap_container flex flex-col">
<div class="">
<span class="hands">👏</span>
<span class="count" id="header_count" hx-swap-oob="true">({ countString(count) })</span>
</div>
</div>
}
templ ClapCount(id, count int) {
<span class="count">({ countString(count) })</span>
}
templ ClapButton(id, count int, clapped bool) {
if clapped {
@Clapped(count)
} else {
@Clap(id, count)
}
}
templ Clap(id, count int) {
<div id="clap" name="clap" class="clap_container">
<form class="clap flex flex-col text-center">
<button class="clap_button" hx-post={ clapURL(id) } hx-trigger="click" hx-target="#clap">Clap!👏</button>
<div class="p-4">
<span>👏</span>
<span id="count" class="count">({ countString(count) })</span>
</div>
</form>
</div>
}
templ ClappedWithHeader(count int) {
@Clapped(count)
<span id="header_count" class="count" hx-swap-oob="true">({ countString(count) })</span>
}
templ Clapped(count int) {
<div id="clap" name="clap" class="clap_container ">
<form class="clap flex flex-col text-center">
<button disabled class="clapped_button">Thanks!🎉</button>
<div class="p-4">
<span>👏</span>
<span id="count" class="count">({ countString(count) })</span>
</div>
</form>
</div>
}
Nice simple, type safe golang front end component with reactivity, yes please. 😁
Tailwind
OK, I need tailwind these days - it just makes things much easier and I have found my groove with it - however there was the small task of integrating it with the GO binary and getting everything as part of the build process. For this I added the ‘npx tailwindcss -i assets/css/style.css -o assets/css/app.css –minify’ command to be part of my build process and the css required was generated and minified.
Development Process
To keep the feedback loop nice and quick I’ve been using air to keep the UI and the backend refreshed and in sync while I edit the source. This is super helpful and builds the templ templates when it detects any changes I even switch it on sometimes for my css files.
Build Process
For my build process I’ve settled on Make, its simple does everything I need to do and is well known. It also allows the integration of the code generation steps right into the build process. For the structure of my make file I usually go for my run command and my build commands. For my run commands I separate those out into local running and in a container. So we have ‘run’ ‘run-docker’ ‘stop-docker’, ‘run’ runs the last local binary built in the ‘dist’ directory and binds to local port ‘8080’, where ‘run-docker’ and ‘stop-docker’ are mere aliases for the standard docker compose up/down commands. To make sure I keep track of the binaries and what I think is just good practice is the building into the binary the build date and the git sha of the code that it was built from. To achieve this I added some //go generate
comments and also the //go embed
to the code base. They are:
//go:generate sh -c "date > build.date"
//go:embed build.date
var build string
//go:generate sh -c "printf %s $(git rev-parse HEAD) > git.sha"
//go:embed git.sha
var gitsha string
These can then be logged or served on an API or what ever way make sense to surface the data. I just have them printed on server start up. Makes things easy to keep track of.
Summary
Overall I’ve really enjoyed the HTMX framework and the quick feedback loop got set up with air and the make file for the larger scale tasks. Think I will keep the blog running this for the foreseeable until I get bored again.
Code
The code for all of this can be found on my github.