Trakora brand logo

From Express to Go – My Journey to Minimalist HTTP APIs

By Markus
Backend Development
10.06.2025
Go HTTP API Development Tutorial - Building APIs without External Dependencies

Preface: The entire code I'm talking about is published here: https://github.com/trakora/production-go-api-template

Pull requests and issues are welcome.

The Starting Point

I came from the JavaScript world. When I needed an API, I typed:

npm install express
node index.js

Quickly mocked, quickly deployed. For every problem, there was a nice ready-made library that took care of everything. This worked well until I eventually had no clue what was actually happening in the background.

JS (and yes, TypeScript too) seduces you into quick solutions and sloppy code, at least in my case. Many of my projects don't have 10 developers and architects.

It was clear to me: I need a change of perspective.

I had the choice between Go and Rust. However, since Rust had a much higher learning curve, Go was the choice. Am I one hundred percent happy with it? No idea. But one thing I can say: It's much more fun than the old Node mess.

First Encounter with Go HTTP

When I started, there was practically nothing comfortable in the standard library. With Go version 1.22, I could really get started.

After "Hello, world" I stumbled upon terms like ServeMux and HandlerFunc. In Node I had app.get("/posts/:id").

In Go I had to cobble this together:

mux := http.NewServeMux()
mux.HandleFunc("/posts/", postHandler)

All the articles I read used libraries like chi or gorilla/mux. But this time I wanted to know how far I could get with just the standard library and not permanently rely on external projects.

Three Articles That Helped Me

I found three articles that provided the foundation for getting started.

All three essentially say: do everything with net/http first, before reaching for a library.

My Minimal Prototype

package main

import (
	"log"
	"net/http"
)

func main() {
	mux := http.NewServeMux()

	mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("pong"))
	})

	log.Println("listening on :8080")
	log.Fatal(http.ListenAndServe(":8080", mux))
}

No routing framework, no JSON helper. The thing runs, and I understand every line. Through the article Backend Basics I was also able to dive deep for the first time and understand how HandleFunc works.

The standard library code is excellently written and immediately conveys what's happening.

From Prototype to Template Repo

More endpoints → more chaos.

As more endpoints were added, I needed structure: Config, Logging, Middleware. This led to the Production-Go-API-Template. Folders like /cmd, /api, /pkg and clean separation into Handler → Service → Repository.

Why I Use So Few Libraries

When I avoid external libraries, I gain three things – and noticeably so:

Control

I know every line running on my server. No magic, no hidden side effects. When a bug occurs, I can trace it instead of first searching through the issues tab of a GitHub project.

Fewer Upgrades

With npm install I don't even understand the thousand upgrades I'd have to perform daily. Nodejs Meme

My build pipeline stays lean, and I don't have to check every few weeks whether version 3.4.2 of a framework brings another breaking change.

Better Learning

Those who commit to the standard library get Go explained practically in source code. Every handler, every interface is right there in black and white. This forces understanding – and ensures that I can later actually judge when an additional library is worthwhile and when it's not.

In short: Less plug-and-play, more insight. And that's exactly what makes the code more robust in the end and makes me a bit more confident as a developer.

Conclusion

Express was comfortable, but Go forces me to consciously decide: Which dependency is really necessary? The standard library easily covers 80 percent. For the rest, I choose libraries deliberately. My template repo grows organically without having to upgrade a framework every few weeks. I'm happy with the decision and will apply this doctrine to other projects in the future.