Skip to content
gohacks.dev
APIs

The Best Go Libraries for Building APIs Compared by Task

Pick Go libraries for each API task: router, OpenAPI, validation, database, auth, limits, tracing and tests, with the Go version each latest release needs.

8 min read
Summarize in AI
Flat illustration of a browser and a server trading a request and a response.

Ask ten Go developers which library to use for an API and you get ten answers and an argument about whether you need a library at all. The honest position is in between. The standard library covers more than most languages' do, and a handful of well-chosen libraries cover the rest. The mistake to avoid is picking a framework first and discovering what it does second.

Tested with Go 1.25.6, module metadata read from the Go module proxy on 2026-10-27

This article goes through the tasks an API has, names the options that I would consider for each, and gives the criteria for choosing. Where it can, it uses data and not opinion: the table below was produced by asking the Go module proxy for the latest release of each library and reading its release date and the go directive in its go.mod. That last column matters more than it looks, and it is the first thing to check. The check ran on Go 1.25.6.

Check the Go version before you read the rest

Shell
printf '%-44s %-12s %-12s %s\n' module latest released go-directive
for m in \
  github.com/go-chi/chi/v5 github.com/gin-gonic/gin github.com/labstack/echo/v4 github.com/gofiber/fiber/v3 \
  github.com/danielgtaylor/huma/v2 github.com/oapi-codegen/oapi-codegen/v2 github.com/ogen-go/ogen \
  github.com/go-playground/validator/v10 github.com/jackc/pgx/v5 github.com/sqlc-dev/sqlc gorm.io/gorm entgo.io/ent \
  github.com/golang-jwt/jwt/v5 github.com/redis/go-redis/v9 github.com/rs/zerolog go.uber.org/zap \
  go.opentelemetry.io/otel github.com/prometheus/client_golang github.com/stretchr/testify golang.org/x/time; do
  go list -m -f '{{.Path}} {{.Version}} {{.Time.Format "2006-01-02"}} {{.GoVersion}}' "$m@latest" 2>/dev/null | awk '{printf "%-44s %-12s %-12s %s\n", $1, $2, $3, $4}'
done
Text
module                                       latest       released     go-directive
github.com/go-chi/chi/v5                     v5.5.0       2026-10-14   1.24
github.com/gin-gonic/gin                     v1.12.0      2026-02-28   1.25.0
github.com/labstack/echo/v4                  v4.16.2      2026-10-23   1.26.0
github.com/gofiber/fiber/v3                  v3.6.1       2026-10-14   1.26.0
github.com/danielgtaylor/huma/v2             v2.40.0      2026-10-27   1.26.0
github.com/oapi-codegen/oapi-codegen/v2      v2.8.1       2026-10-24   1.25.0
github.com/ogen-go/ogen                      v1.24.0      2026-08-07   1.25.0
github.com/go-playground/validator/v10       v10.30.5     2026-09-20   1.26.0
github.com/jackc/pgx/v5                      v5.11.0      2026-09-07   1.25.0
github.com/sqlc-dev/sqlc                     v1.31.1      2026-04-22   1.26.0
gorm.io/gorm                                 v1.31.2      2026-06-22   1.18
entgo.io/ent                                 v0.14.6      2026-03-17   1.24
github.com/golang-jwt/jwt/v5                 v5.3.1       2026-01-28   1.21
github.com/redis/go-redis/v9                 v9.23.0      2026-10-05   1.26.0
github.com/rs/zerolog                        v1.35.1      2026-04-20   1.23
go.uber.org/zap                              v1.28.0      2026-04-28   1.19
go.opentelemetry.io/otel                     v1.47.0      2026-10-02   1.26.0
github.com/prometheus/client_golang          v1.25.0      2026-10-08   1.26.0
github.com/stretchr/testify                  v1.12.1      2026-08-17   1.17
golang.org/x/time                            v0.16.0      2026-08-19   1.26.0

Read the last column as "the oldest Go toolchain this release will build with". On 27 October 2026, with Go 1.25.6 installed, nine of these twenty latest releases ask for go 1.26. Run go get for any of them without a pin and the toolchain either downloads a newer one (if GOTOOLCHAIN allows) or stops with an error. Neither is what you want to discover in CI on a Friday.

Three consequences. Pin dependency versions in go.mod and commit go.sum. When you must stay on an older toolchain, go get module@version for the last release that supports it, which is how the other articles in this series ended up with specific versions. And add the minimum Go version of each dependency to your upgrade checklist, because Go upgrades and library upgrades are linked. The Go project's toolchain documentation describes how the go and toolchain lines interact.

The remainder of the article is about what each library does, not about which version is newest.

Routing and middleware

The standard library. Since Go 1.22, http.ServeMux handles methods and wildcards ("GET /items/{id}"), reads values with r.PathValue, and answers 405 for the wrong method. For many services it is enough, with middleware written as plain func(http.Handler) http.Handler. See the standard library as a framework.

chi (go-chi/chi) adds nested routes, route groups with their own middleware and a good set of middleware. Its handlers are http.Handler, so nothing in your code is chi-specific except reading parameters. It is the safe choice when the standard mux feels thin, and the todo API uses it.

gin and echo are the popular frameworks. Both give you binding, validation hooks, a context object with helpers and a large ecosystem. The price is a handler signature that is not http.Handler, which ties your code to the framework; gin's latest release builds on Go 1.25 and echo's asks for 1.26. If you have an existing gin application, migrating to the standard mux is possible and not urgent.

fiber is built on fasthttp, not net/http. That is where its speed comes from and also the reason many net/http libraries (middleware, OpenTelemetry instrumentation, test helpers) do not work with it directly. Choose it with that knowledge.

How to decide: if you want to keep handlers as http.Handler and standard context, use the standard mux or chi. If your team already knows gin and likes it, keep it. For a comparison with measurements, see Go web frameworks compared.

OpenAPI and typed APIs

Three approaches, each with a leader.

Spec first, generate the server: oapi-codegen reads an OpenAPI document and generates server interfaces and types for net/http, chi, gin and others. ogen generates a more opinionated server and client, with its own router and validation. Both fit teams that treat the spec as the contract (OpenAPI shared contracts).

Code first, generate the spec: huma builds the OpenAPI document from typed Go handlers, so the description cannot drift from the code. It supports chi, gin, echo, the standard mux and others. See code-first frameworks.

Neither: hand-written handlers with a documented JSON shape. Fine for small internal APIs.

If you use generated clients (client generation), make sure generators and the spec version are pinned.

Validation

go-playground/validator validates structs with tags (validate:"required,email,max=100") and is the default almost everywhere, including as the engine inside gin. Its strengths are the large rule set and cross-field rules. Its weakness is that rules live in tags as strings, which the compiler cannot check, so a typo in a tag is a runtime surprise (validator in practice). The alternative is explicit validation code in a Validate() error method on the request type, which is a few lines longer per type and fully type-checked. For small APIs I prefer the explicit version; for large ones with hundreds of fields, the tags save real work.

Database access

Use database/sql with a driver, or pgx directly, for PostgreSQL; pgx's native interface is faster than the database/sql adapter and supports PostgreSQL features (COPY, LISTEN/NOTIFY, batch queries). On top of that:

  • sqlc generates type-safe Go from SQL queries you write. You keep control of the SQL and get compile-time checking. Best default for teams comfortable with SQL (sqlc).
  • GORM is a full ORM with associations, hooks and migrations. It is fast to start with, easy to misuse and hides queries. Use it knowing that.
  • ent generates a typed graph-style API from schema definitions, with strong support for relations. It suits models with many relationships.
  • Plain database/sql with small helper functions is entirely respectable.

Whatever you pick, learn the SQL it produces, test against a real database (integration tests), and pin migrations in a tool.

Authentication and authorisation

golang-jwt/jwt is the standard JWT library. Use its parser options to pin the allowed algorithms and validate issuer, audience and expiry; the library declares a Go 1.21 minimum, so it rarely forces upgrades (API authentication). For OAuth 2 clients, golang.org/x/oauth2 is the reference (OAuth 2 basics). For authorisation policies, Casbin and Open Policy Agent exist, and many APIs get by with a small policy function in one package.

Rate limiting and shared state

golang.org/x/time/rate is a good in-process token bucket. For limits across instances, Redis through go-redis and a Lua script (Redis rate limiter). go-redis is also the client for caches and queues (API caching).

Logging and observability

The standard library's log/slog is a structured logger and should be your default; zerolog and zap remain faster in microbenchmarks and have mature ecosystems, and both can be used behind slog handlers. OpenTelemetry covers traces and metrics (tracing), and prometheus/client_golang is the usual metrics exposition library (SLOs).

Testing

The standard testing and httptest packages cover handlers (handler tests). testify adds assertions and suites; many teams like it, and the standard library is enough for those who prefer explicit comparisons (testify or the standard library). go-cmp gives readable diffs (cmp.Diff). Testcontainers helps start real databases in tests (Testcontainers).

A small default stack

If I were starting a typical JSON API on PostgreSQL today, I would begin with: the standard mux or chi, pgx with sqlc, golang-jwt if tokens are needed, log/slog, x/time/rate or Redis for limits, OpenTelemetry for tracing, and the standard testing packages. Add oapi-codegen or huma when the OpenAPI document becomes part of the product. That is five or six dependencies you can name and defend, each with a reason.

How to evaluate any library

A short list of questions beats a ranking.

  1. What does it need from Go? The table above is the first check.
  2. Is it maintained? Recent commits and releases, issues answered, more than one maintainer.
  3. How big is the surface it forces on you? Libraries that take over your handler signature are expensive to remove.
  4. Does it compose with net/http, context and slog?
  5. What does it do on failure? Read its error paths and its timeouts.
  6. What does it pull in? go mod graph shows the transitive cost.
  7. Can you replace it? If removing it means a rewrite, you are adopting a framework, so decide that deliberately.

The module proxy answers the first questions in seconds with go list -m -json module@latest, and pkg.go.dev shows imports, versions and licences for any package.

Also inWeb
Taggedroundup
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.