# Building Gin: Simple Over Easy

> A stalled startup in 2014 became Gin: a Go web framework shaped by simple-over-easy design, a radix-tree router, and an API that still works ten years later.

[View on manualmeida.dev](https://manualmeida.dev/articles/gin-simple-over-easy)

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

The answer was Fyve, a social network built around people's interests. I chose [Go](https://go.dev/)
for the backend because the language felt plain in the right way. [Gin](https://gin-gonic.com/) started
as the web framework for that product, written while I was still learning Go and still deciding what kind
of engineer I wanted to be. The code lives at [gin-gonic/gin](https://github.com/gin-gonic/gin).

<figure class="article-figure">
  <img
    src="/articles/gin/golang-framework-gin-gonic.png"
    alt="Gin Gonic Go framework illustration"
    loading="eager"
  />
</figure>

Fyve faded. Gin kept going.

## Simple over easy

At the time, the Go web framework people kept pointing me to was
[Martini](https://github.com/go-martini/martini). I understood why immediately. The README was small,
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
feel smooth. It also moved important behavior out of sight. Services appeared in handlers by magic,
control flow became harder to trace, and reflection ran on the request path.

I was reading the Go community through Rob Pike's
[Simplicity is Complicated](https://www.youtube.com/watch?v=rFejpH_tAHM) lens around then. The line that
stuck with me was not a slogan about minimalism. It was the cost model: simple software often takes
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,
nicer syntax, less ceremony on the page. **Simple is lower count:** fewer moving parts, fewer concepts to
learn, fewer exceptions to remember. Martini made the first version easy. I wanted Gin to stay simple
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
of the framework problem too.

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

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

```go
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")          // path params, no reflection
    c.JSON(200, gin.H{"id": id}) // response rendering, one call
})
r.Run(":8080")
```

That `*gin.Context` carries the request, response writer, path parameters, validation helpers, and
rendering. You pass one thing around. The obvious operations stay one method call away, while the
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
[`context.Context`](https://pkg.go.dev/context). When it landed, we didn't rename ours — we made
`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
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
expressions and ask each one whether it matched. That is flexible. You can make a route match only
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
the router faster because it could use a [radix tree](https://en.wikipedia.org/wiki/Radix_tree), the same
approach [httprouter](https://github.com/julienschmidt/httprouter) made popular in Go. More importantly, it made routes easier to reason about. The framework pushed you
toward regular paths instead of clever matchers.

> *[Interactive figure — view it at https://manualmeida.dev/articles/gin-simple-over-easy]*

Matching `/blog/42/comments` walks `/blog/` down to the `:slug` node, binds `42`, and continues into
`/comments`. The work follows the length of the URL, not the number of routes registered in the app.
Routes that look separate in a file collapse into one compact path through memory.

For a router holding $n$ routes and a request path of length $k$, a radix-tree lookup runs in

$$
T_\text{match}(k) = O(k), \quad \text{independent of } n,
$$

where linear regex matching costs $O(n \cdot m)$ for $m$-length patterns. The tree trades that
per-request scan for a single walk down the shared prefix.

The same idea showed up in the small allocation choices. Parameters live in a preallocated slice. Context
objects come from a `sync.Pool` and get reset between requests. The garbage collector gets less junk to
clean up, so latency has fewer reasons to wobble.

**That is the kind of performance work I trust: fewer operations on the hot path, and fewer concepts in
the programmer's head.**

## Designing for zero breaking changes

The quiet goal was zero breaking changes. I wanted Gin to feel like it belonged in Go's ecosystem, which
meant learning from Go's own [compatibility promise](https://go.dev/doc/go1compat).

That constraint changes how you design. You add a method before you remove one. You reject the clever
rename that saves five characters. You treat every public function as something a stranger might build
a company on.

It held. Some of the first programs written against Gin still compile and run more than ten years
later. **The benchmarks matter. That compatibility matters more.**

## Hacker News, and then growth

I [released Gin on Hacker News](https://news.ycombinator.com/item?id=7966700) at the right moment. Go was
getting attention there, and a framework that fit in one README, avoided reflection on the hot path, and
benchmarked well was easy to try.

The growth after that was steady rather than cinematic. People used it, filed issues, sent patches,
and put it in real services. Today Gin sits around 88k stars with more than 290k projects depending on
it.

Open source numbers can be vanity metrics. In this case, **the dependency count matters more to me than
the stars.** It means the API decisions kept compounding long after the original startup disappeared.

## Letting it graduate

A few years in, I stepped back and handed Gin to maintainers who kept improving it without me. I think
of that as the project graduating. Special kudos to [Bo-Yi Wu](https://github.com/appleboy) and
[Javier Provecho](https://github.com/javierprovecho), who carried it forward and kept the bar high.

The part of open source I respect is when **the thing stops needing its author.** It survives contact
with other people's use cases, other people's priorities, and other people's taste.

If you're building a library, that is the bar I would aim for. Design the API you can imagine keeping
for ten years. Make it simple underneath, even when that costs more than making it easy. **Build it so it
can outgrow you.**

I tried to do the same thing a few years later on a compiler, which is the story of
[Qwik](/articles/qwik-resumability).

---

## About the author

Manuel Martínez-Almeida (Manu) is a principal engineer at Builder.io, where he sets the technical direction for the AI agent platform across product, design, and code. He is the creator of Gin (the Go web framework with ~88k GitHub stars and 290k+ dependent projects), helped take Qwik from proof-of-concept to production including its Rust compiler and resumability model, and designed the compiler architecture and virtual DOM for Stencil. His work sits where performance, systems, and developer experience meet — compilers, language runtimes, web frameworks, GPU rendering, and distributed infrastructure serving billions of requests a day. He is based between Lisbon and Budapest.

- **Name:** Manuel Martínez-Almeida (Manu)
- **Role:** Principal Engineer at Builder.io
- **Focus:** compilers, language runtimes, web frameworks, GPU rendering, distributed infrastructure, developer experience, AI agents, open source
- **About:** https://manualmeida.dev/articles/about/
- **GitHub:** https://github.com/manucorporat
- **Twitter/X:** https://x.com/manucorporat
- **Website:** https://manualmeida.dev
