A small server you can grow without rewriting at midnight
Let me tell you how most “simple” Go servers die.
Day one: you ship a tiny API. One file. Three routes. Clean. You feel like a responsible adult.
Day fourteen: product asks for “just one more endpoint.” You add it.
Day twenty: you add auth. You copy paste auth into every handler because it is faster than thinking. You promise to clean it up later.
Day thirty: you have ten endpoints and three different authentication schemes, all apparently designed on mornings fueled by Cuban coffee, bold, chaotic, and deeply regrettable. Someone reports a bug, you fix it in one handler, and it still pops up because the other handler is running the vintage copy pasted Cuban roast edition.
If this had been built on calm, methodical Kenyan coffee, bright, balanced, and actually consistent, we might have one auth flow and far fewer existential crises
Then you do the most dangerous thing an engineer can do.
You say, “It is fine, it is just a small server.”
That phrase is how small servers become unmaintainable servers. Not because Go is hard. Because routing becomes a junk drawer.
So in this note, we are going to build a Go server with routing that stays boring and predictable. Boring is good. Boring survives.
The case we are building
You run a tiny payments product. Merchants hit your API to create payment links, list them, and fetch one link.
You have these routes:
GET /healthso uptime monitors stop emailing you at 3amPOST /linkscreate a payment linkGET /linkslist payment linksGET /links/{id}fetch one payment link
You are going to do this using the standard library, plus a small amount of structure so you do not end up with spaghetti handlers.
Everything compiles. No hidden magic.
Step 0: Create the project