Programming

Gin as our default framework for small Go APIs

PRG

As we started writing more Go services this year, we evaluated a few HTTP frameworks before settling on Gin as our default for anything beyond the simplest use of the standard library's `net/http`. The evaluation itself was fairly quick — we tried Gin, Echo, and a plain standard-library setup on a small internal prototype, timing how long it took to reach a working baseline with routing, middleware, and JSON handling in place.

Coming from an Express background, Gin's routing and middleware conventions felt immediately familiar — route groups, middleware chains, and a context object passed through the request lifecycle map cleanly onto patterns we already knew. That familiarity mattered more than any specific benchmark difference between the frameworks we tried, since it meant the whole team could be productive in it within a day rather than needing to internalize an unfamiliar set of conventions on top of learning Go itself.

Its JSON binding and validation helpers also removed a fair amount of boilerplate we would otherwise write by hand for every incoming request. Struct tags define validation rules directly alongside the type definition, and a single `ShouldBindJSON` call handles parsing and validating an incoming request body against those rules, returning a clear error if anything does not match — a pattern that replaced what had been repetitive manual validation code in our very first Go services, written before we had settled on Gin at all.

Where we still reach for the standard library instead

We still reach for the plain standard library on very small services where even Gin's modest footprint feels like more than we need — a health-check endpoint for an internal tool, for instance, genuinely does not need a routing framework. But for anything with more than a couple of routes, Gin has become the sensible default, and every new Go project now starts from a small internal template with Gin, structured logging, and our standard error-handling middleware already wired in, mirroring the same instinct that led us to build a similar starter template for Express projects.

A concrete comparison that settled the decision

To make the framework choice less subjective, we built the same small internal service three separate times — once in plain `net/http`, once in Echo, and once in Gin — and had two engineers unfamiliar with all three frameworks build a new endpoint in each, timing how long it took and noting where they got stuck. Gin came out ahead not on raw performance, where all three were close enough not to matter for our actual traffic levels, but on how quickly an engineer unfamiliar with the framework could become productive in it, which mattered more to us given how often we bring new team members onto Go projects who have more Express experience than Go experience specifically.

Middleware patterns we standardized on

Once Gin became the default, we built a small set of shared middleware mirroring what we already had for Express — request logging, centralized error handling, and a request-ID propagation middleware — so a Go service and a Node service in the same client's stack follow recognizably similar conventions at the API layer, even though the underlying languages are different. This has made it noticeably easier for an engineer moving between a client's Go and Node services within the same week to stay oriented, without needing to relearn a completely different set of cross-cutting conventions for each language.

← Back to the journal

Have a project in mind?
Let’s talk.

Tell us where you are and where you want to go. We'll map the fastest route between the two.

Currently accepting new clients