Building Gin: Simple over Easy

manucorporat2 pts1 comments

Building Gin: Simple Over Easy — Manu Martínez-Almeida

Open Source<br>Building Gin: Simple Over Easy<br>June 30, 2026 · Manu Martínez-Almeida goperformancehttpopen source

In 2014 I came back from San Francisco with no plan and one useful scar: I had seen what small<br>software teams needed from their tools. I had spent a year building SDKs at Joypad and TinySpark after<br>shipping one of my first games. Then I was back in Spain, about to start Telecommunications<br>Engineering, and trying to decide what to build next.

The answer was Fyve, a social network built around people’s interests. I chose Go<br>for the backend because the language felt plain in the right way. Gin started<br>as the web framework for that product, written while I was still learning Go and still deciding what kind<br>of engineer I wanted to be. The code lives at gin-gonic/gin.

Fyve faded. Gin kept going.

Simple over easy

At the time, the Go web framework people kept pointing me to was<br>Martini. I understood why immediately. The README was small,<br>the middleware model felt elegant, and you could get a route responding in minutes.

Martini used reflection-based dependency injection to wire handlers together. That made the first demo<br>feel smooth. It also moved important behavior out of sight. Services appeared in handlers by magic,<br>control flow became harder to trace, and reflection ran on the request path.

I was reading the Go community through Rob Pike’s<br>Simplicity is Complicated lens around then. The line that<br>stuck with me was not a slogan about minimalism. It was the cost model: simple software often takes<br>more work from the person building it so it can take less work from the person using it.

That became the design line for Gin. Easy is what looks good in the first example: fewer lines,<br>nicer syntax, less ceremony on the page. Simple is lower count: fewer moving parts, fewer concepts to<br>learn, fewer exceptions to remember. Martini made the first version easy. I wanted Gin to stay simple<br>after the codebase was old enough to surprise me.

Finding the middle ground

Aristotle’s version of virtue was the middle ground: not too much, not too little. That was the shape<br>of the framework problem too.

Martini gave you too much magic. Go’s standard net/http gives you the<br>honest version of web programming: no magic, explicit control flow, and a handler shape you can hold in<br>your head. But it gives you too little help. You write the same plumbing for route params, request<br>parsing, validation, and responses in handler after handler. None of it is hard. Enough of it becomes<br>noise.

Gin was my attempt to find the useful point between them: keep the request path explicit, run no<br>reflection there, and put the boring work behind one object you can inspect: the Context.

r := gin.Default()<br>r.GET("/users/:id", func(c *gin.Context) {<br>id := c.Param("id") // path params, no reflection<br>c.JSON(200, gin.H{"id": id}) // response rendering, one call<br>})<br>r.Run(":8080")<br>That *gin.Context carries the request, response writer, path parameters, validation helpers, and<br>rendering. You pass one thing around. The obvious operations stay one method call away, while the<br>machinery remains visible enough that you can debug it when production gets weird.

Funny enough, gin.Context shipped in 2014, two years before Go’s standard library had a<br>context.Context. When it landed, we didn’t rename ours — we made<br>gin.Context satisfy the standard interface, adding full compatibility without a single breaking change.

That was the product sense from SDK work showing up in a web framework. Developer experience is a<br>feature when it makes the fast path feel natural.

A router built around a radix tree

The router is where the simplicity line became concrete. Martini could walk a list of regular<br>expressions and ask each one whether it matched. That is flexible. You can make a route match only<br>numbers, or hide extra rules inside the pattern. It is also another language inside your framework.

Gin chose a smaller route language: static segments, named parameters, and catch-alls. That choice made<br>the router faster because it could use a radix tree, the same<br>approach httprouter made popular in Go. More importantly, it made routes easier to reason about. The framework pushed you<br>toward regular paths instead of clever matchers.

Route lookup, without scanning routes

routes 0 nodes 1 matched /

Figure. A compressed radix tree, built and matched live. Shared prefixes collapse into one path, then /blog/42/comments walks the tree and binds 42 as :slug.<br>Start with ordinary routes<br>The router sees route strings like /search, /support, and /blog/:slug/comments. A naive router can test them one by one. A radix tree turns the shared text into structure.

Shared prefixes collapse<br>/search and /support share /s. The two blog routes share /blog/ and then :slug. Each shared prefix is stored once.

Matching walks one path<br>For /blog/42/comments, the lookup follows /blog/, binds 42 at :slug, then continues into /comments. It...

simple path context easy framework route

Related Articles