What I learnt building w.bradshaw.page in Go

Written by Human, Not by AI

A personal website is a common project for new and experienced programmers alike. The blank slate that is an HTML document leads developers to an infinite number of websites of varying complexities. Having created and discarded two of my own before, I knew this website would lose its shine quickly—but that didn’t stop me from attempting to build on what I thought would be the Ultimate Tech Stack.

After watching many hours of The Primeagen, I knew that JavaScript was the devil and Rust is king. My desire to leave the JavaScript ecosystem led me to proclaim that I would use Go and the much-revered HTMX instead. Coming from the land of Vercel, TypeScript, and JavaScript frameworks, this was a transition sure to teach me a lot. I laced up my boots, init’ed a Git repo, and made a very hopeful Google search: “golang web framework”.

Of course, I was yet to learn that the sanctum of JavaScript does things quite differently to other programming languages. In Go, a web framework is more of an http router and less of a towering monolith of minified code and build steps that somehow results in a hosted URL on the internet. None of this I knew as I chose the one I saw first (chi), and joined the hoard of other confused JavaScript and Python developers in r/golang who desperately wanted to leave their ecosystems, but didn’t want to learn anything new.

In Vercel JavaScript land, the idea of a function is so far abstracted away from the developer experience that I didn’t even realise that my API routes were separate serverless functions in Vercel’s cloud. The question of a monolithic or micro-services architecture hadn’t even crossed my mind previously.

While I had used AWS Lambda before, I wasn’t confident in building a website using it. This led me to various experiments involving managed Kubernetes, plain old EC2, and other cloud providers that ultimately concluded I ought to just learn more about AWS lambda.

The universal platform that is IaaS presents limitless possibilities and little guidance on how to navigate them. In my experience, a Google search regarding AWS will inevitably return ~5 official AWS documentation pages, with only one of them partially answering your question. It seems that the amount of AWS documentation slowly accumulates on the internet, poisoning the Google searches of aspiring engineers who don’t need to be led astray by the documentation for a superseded AWS service that is only still supported for its existing customers.

I’ve since learnt that provisioning your resources manually using the AWS web console is generally a bad idea. Ideally, your stack shouldn’t be specific to your single AWS account, rather, it should be declared somewhere that means it is able to be replicated. Because I wanted to do things the AWS way, I consulted their documentation and found AWS Cloudformation. And then I found the AWS SAM CLI. And then I found the AWS CDK. This took me several weeks of experimentation.

Once I delved into using AWS Lambda configured using the AWS CDK (which also features documentation sprawled across the GitHub mono-repo, ’experimental’ and stable versions of the API, the AWS website, the AWS CDK reference, and ‘ConstructHub’ (what is that)), I realised that two paths lay ahead of me. Either I use API Gateway as my router to many Lambda functions that each handle a different page, or I forward all requests to a single Lambda that has its own router. Given I had already chosen chi as my router, I decided to take the monolithic approach for my relatively small application.

Stranded in a sea of AWS, I started to look for any familiarity in my stack. Against the advice of the many who recommend using only Go templates, I decided to use templ. templ is a tool that seems to take inspiration from JSX. It offers a DSL where one can write templ ‘components’ that are compiled into Go functions when the templ CLI is invoked. Also, its documentation is solid and offers some concrete examples for beginners to use (which I ended up cloning and adapting to my use case).

All of the pieces to build my site were now in hand, I just needed to pull them together. Coming from a background of frameworks, I found it challenging to not have my project architected for me. I missed file-system routing and simple niceties such as ’lib’ and ‘public’ directories. Having to architect my own project is a problem I haven’t dealt with, and one that I came to love. In building this site I felt like I was creating my personal framework, desperately trying (and inevitably failing) to build reusable abstractions. Dependency injection, accepting interfaces rather than structs, and ‘clean code’ became a bit of an obsession. This side of programming is something I thought I knew, but I realise now that I didn’t. It puts JavaScript frameworks into perspective. Contemporary frameworks feel so far removed from JavaScript that they are closer to scripting languages than they are to programming.

Now that my site is complete, I understand the common advice given to all of the confused developers of r/golang. In trying something new, it is important to fully embrace its way of doing things. While the essence of JavaScript frameworks can be seen in my attempt to recreate file-system routing, and my use of templ rather than vanilla Go templates, I now feel empowered to fully embrace Go in my next project. Its not easy to forget the familiarity of technologies you already know, but it is crucial that we all continue learning and challenging ourselves to think in new ways. It is the insatiable desire for the Ultimate Tech Stack that brought me here, and it is something I clutch on to dearly.