Framework
smallnest/go-web-framework-benchmark avatar
smallnest/go-web-framework-benchmark

go-web-framework-benchmark: a 2020 table over a 2026 dependency set

:zap: Go web framework benchmark

2,133 stars224 forksGoApache-2.0

At a glance

What is it?
smallnest's suite starts a one-route HTTP server inside each Go web framework and times the whole request under wrk, instead of timing route lookup alone. Its published table was measured in May 2020 on go1.18.5, while go.mod now declares go 1.26.5 and pins framework versions dated years later.
Who is it for?
Read this suite as a map of Go framework architecture, not as a purchasing decision for 2026. It earns its keep if you want to see how a whole request behaves under 5000 clients with a delay you control, and it cannot tell you which framework is fastest on a current stack, because its published rows stopped at 2020-05 while go.mod moved on without them.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 101 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 15, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why this suite times whole requests, not routes

The project begins as a correction to someone else's benchmark. Julien Schmidt's go-http-routing-benchmark compares routers, holding the handler trivial so that the only variable left is how fast a framework matches a URL to a handler. The author here, smallnest, had taken those router scores at face value until a mock business handler changed the picture: with real work inside the handler, the cost of route selection stopped being where request time went. So this repository measures connection handling, route selection and handler execution as one number, because that is what an application pays on every request. For engineers comparing stacks, that difference decides whether a router table is useful at all. Two frameworks can match routes at identical speed and still separate widely once a handler does anything, and only a whole-request harness can show it.

One GET route and a sleep that fakes business logic

Every framework under test is started as a real HTTP server with exactly one registered route, GET /hello, and the handler does two things: wait, then reply. The wait is read from the first command line argument with `strconv.Atoi(os.Args[1])` and counted in milliseconds, so the delay changes per run without editing any source. The reply is the fixed text `Hello world`. What the wait stands in for is spelled out in the project: reading data from sockets, writing data to disk, accessing databases, accessing cache servers, invoking other microservices.

go
api.Get("/rest/hello", func(c *XXXXX.Context) {
	sleepTime := strconv.Atoi(os.Args[1]) //10ms
	if sleepTime > 0 {
		time.Sleep(time.Duration(sleepTime) * time.Millisecond)
	}

	c.Text("Hello world")
})

That is the entire handler, and the context type stays as XXXXX.Context in the listing instead of borrowing one framework's real name. Compile a binary with a delay in argument one, start the server, point wrk at it, and whatever latency remains after subtracting your delay is the framework's own cost of accepting, routing, writing and closing.

Four mock delays, 5000 clients, one cpu-bound pass

Three published passes cover the space between a trivial handler and an I/O-bound one. The basic case mocks 0 ms, 10 ms, 100 ms and 500 ms of handler time with 5000 concurrent clients, and its charts split the outcome into throughput, latency and allocations. Latency is defined as the real processing time reported by the web server, smaller is better; allocs is the heap used by the server while the run is in flight, measured in MB, also smaller is better. The concurrency case pins the handler at 30 ms and moves the client count across 100, 1000 and 5000, which separates frameworks that fall apart under many connections from frameworks that do not. A third pass targets the cpu-bound case, where the work is computation rather than waiting. Published pipelining runs cover the basic and concurrency passes, because HTTP pipelining changes how a client reuses a connection. Results land in two plain CSV files, processtime.csv and concurrency.csv, and the charts in the repository are static images that nothing regenerates for you.

Install, build gowebbenchmark, then run test.sh

Start by fetching the module. This step downloads a large number of framework dependencies and takes a while:

bash
go get github.com/smallnest/go-web-framework-benchmark

Once it finishes, move into the checkout, compile a single binary named gowebbenchmark, and start the run:

bash
cd $GOPATH/src/github.com/smallnest/go-web-framework-benchmark
go build -o gowebbenchmark .
./test.sh

What to expect afterwards: wrk drives load against each framework's server in turn, and the two CSV files appear in the working directory. Budget the time for the download rather than the measurement, since the dependency graph pulls in every framework under test. Keep in mind that go.mod declares a newer Go than the one behind the published table, so the binary you build here is not the binary that produced those numbers.

Shell scripts vary the client, the connection and the list

Six scripts at the top level change exactly one variable each. test.sh is the plain throughput pass. test-latency.sh adds the latency and allocation measurements. test-latency-nonkeepalive.sh drops connection keepalive, which isolates how much of a good result came from reusing sockets rather than from the framework itself. test-pipelining.sh enables HTTP pipelining, and test-all.sh runs the full set; docker-test.sh performs the same work inside a container, where the runtime image installs wrk and gnuplot and declares a volume at /data. libs.sh and pipeline.lua round out the scripts at the top level. The choice of frameworks is a bash array named web_frameworks inside the test script, and editing that array is the documented way to narrow a run. The array and the framework list in the README do not line up. The array carries entries such as gorestful, gorouterfasthttp and tiger, while the README writes go-restful and go-tigertonic, and gorouterfasthttp has no counterpart in the README at all. The README also lists goji twice, once pointing at zenazn/goji/web and once at goji.io, so two distinct projects share a single name in the alphabetical run from atreugo through webgo.

Three toolchains disagree inside a single repository

Here is the contradiction a close reading turns up. The results are stamped Last Test Updated: 2020-05, and the environment is named precisely: a KVM Virtual CPU version at 2 GHz with 4 cores, 16G of memory, go1.18.5 linux/amd64, Ubuntu 22.04.1 LTS with kernel 5.15.0-41-generic. go.mod, at the root of the same repository, opens like this:

code
module github.com/smallnest/go-web-framework-benchmark

go 1.26.5

Its requirements name framework versions that did not exist when that table was taken, from gin v1.12.0 and echo/v4 v4.15.4 through fiber/v2 v2.52.14, fasthttp v1.72.0, chi v5.3.1, gf/v2 v2.10.2 and gorilla/mux v1.8.1, and one of them, github.com/claygod/Bxog, is pinned to the pseudo-version v0.0.0-20260626174902-50694fc4cc62. A third toolchain sits in the Dockerfile:

dockerfile
ENV GOPROXY=https://goproxy.cn,direct
ENV GO111MODULE=on
VOLUME ["/data"]
CMD ["/bin/sh","./docker-test.sh"]

That image builds FROM golang:1.14.3-alpine3.11 and finishes on alpine:3.11, a toolchain older than the Go used for the published run, and it clones the repository with depth 1 before running go mod download. Three different Go versions live in one project, and no combination of them reproduces the 2020 rows. The dependency list has also drifted past the framework list: Bxog, go-json-rest and gocraft/web are required by go.mod yet appear nowhere among the tested frameworks. The last push was on 2026-07-14, the repository carries Apache-2.0, and it has published no GitHub releases, so the module moves without a tagged history to match.

Where the mock stops resembling a real service

Several limits are structural rather than accidental. Only one route is registered, GET /hello, so path matching for parameters, wildcards and method restrictions is barely exercised, and the handler reads no body, inspects no headers and streams nothing. The published passes cover plain HTTP only: no TLS run, no HTTP/2 run, and no database or cache in the loop appear anywhere in the project, which means a framework that is slow at parsing large bodies looks identical to one that is fast. Sleeps also compress the picture, and the project says so itself: once the handler waits, route selection stops being where the time goes, which is precisely why the 500 ms column exists next to the zero column. Pushing 5000 clients at a 2 GHz virtual CPU says as much about the load generator as about the framework. Charts are images, and the two CSVs are the only machine-readable results, so nothing in the repository lets you plot a framework that is missing from the array.

A router-only benchmark answers a narrower question

The real alternative is julienschmidt/go-http-routing-benchmark, the project this one was built to disagree with. It holds the handler constant and varies the routing table, which isolates the cost of matching a URL against many routes and nothing else. That yields a cleaner ranking of routing code and a worse picture of a service, because it never pays for connection handling or handler execution. The same split shows up inside the suite's own rows: fasthttp, fasthttp/router, fasthttp-routing and fiber sit on a different HTTP stack from the net/http family around gin, echo, chi and gorilla, and the row labelled default http is the standard library baseline sitting in the same table. If your service is CPU-bound and does no I/O, the cpu-bound pass is the column to study; if it waits on databases, the 100 ms and 500 ms columns carry more weight than the zero column, and the default http row is the one to beat.

Editorial conclusion

Read this suite as a map of Go framework architecture, not as a purchasing decision for 2026. It earns its keep if you want to see how a whole request behaves under 5000 clients with a delay you control, and it cannot tell you which framework is fastest on a current stack, because its published rows stopped at 2020-05 while go.mod moved on without them. Before adopting a framework on the strength of a row here, re-run test.sh on your own hardware, add test-latency-nonkeepalive.sh to see how much of the number came from socket reuse, and confirm the framework is still listed in the web_frameworks array.

Frequently asked questions

What does go-web-framework-benchmark measure that a router benchmark does not?

It times the whole HTTP request, covering connection handling, route selection and handler execution, where go-http-routing-benchmark times route selection alone. Each framework serves a single GET route at /hello whose handler sleeps a configurable number of milliseconds before replying.

How do I run the benchmark myself?

Fetch the module with go get, change into the checkout under $GOPATH/src, build a binary named gowebbenchmark and run ./test.sh. The run drives wrk and writes processtime.csv and concurrency.csv, and the dependency download takes a while before any measurement starts.

Which web frameworks does the suite cover?

The README lists frameworks in alphabetical order from atreugo through webgo, including the standard library's default http, and states that only stable frameworks are tested. The module file requires more than that list names, with Bxog, go-json-rest and gocraft/web among the dependencies that have no row in the README.

Does it test TLS or HTTP/2?

No published pass covers either. The suite exercises plain HTTP with keepalive on by default, plus non-keepalive, pipelining and cpu-bound variants driven by the shell scripts, and the handler never touches a socket-level option.

How current are the published benchmark results?

The table is stamped Last Test Updated: 2020-05, measured on a 2 GHz KVM virtual CPU with 4 cores and 16G of memory, go1.18.5 linux/amd64, Ubuntu 22.04.1 LTS with kernel 5.15.0-41-generic. The repository's last push was on 2026-07-14, it carries Apache-2.0, and it has no GitHub releases.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. smallnest/go-web-framework-benchmark on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/smallnest-go-web-framework-benchmark.svg)](https://hysenlabs.com/projects/smallnest-go-web-framework-benchmark)