alice

A minimalist Go library for chaining HTTP middleware into a single handler.

Library
Go
vv1.2.0
3,365stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
40/100Fair
Development Activity0
Maintenance0
Community60
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
73/100Good
Architecture88
Code Quality78
Innovation82
Learning Curve45

Alice is a small, dependency-free Go package that replaces deeply nested middleware calls like Middleware1(Middleware2(Middleware3(App))) with a single readable chain: alice.New(Middleware1, Middleware2, Middleware3).Then(App). It doesn’t invent its own middleware type — any function shaped like func(http.Handler) http.Handler already satisfies Alice’s Constructor type, so existing middleware from other packages works without adapters.

Chains built with Alice are immutable: Append and Extend always return a new chain rather than mutating the original, which makes it safe to define a shared base chain (logging, recovery, rate limiting) and branch off route-specific variants without accidentally affecting the shared base. The whole library is a single file of well-documented Go with zero runtime dependencies.

What You Get

  • A Chain type and New() constructor that stores an ordered list of middleware constructors without invoking them until Then is called
  • Then and ThenFunc methods that wrap a final handler (or handler function) in reverse constructor order, treating nil as http.DefaultServeMux
  • Append and Extend methods for building new, immutable chains from an existing one — for shared base chains and route-specific branching
  • Zero external dependencies — the entire library builds on the Go standard library’s net/http package alone

Common Use Cases

  • Standardizing the middleware stack (logging, recovery, auth) applied consistently across every route in a Go HTTP service
  • Branching a shared base chain into route-group-specific variants (e.g., adding admin-only auth) without touching the shared base
  • Replacing manually nested middleware calls (m1(m2(m3(h)))) with a single readable chain declaration
  • Composing third-party middleware constructors (rate limiters, CSRF protection) alongside custom middleware in one ordered chain

Under The Hood

Architecture The entire library lives in a single file, chain.go, exposing one type, Chain, wrapping a slice of Constructor values, where Constructor is func(http.Handler) http.Handler. New copies the given constructors into an immutable slice; Then and ThenFunc apply them in reverse order via a simple loop, so the first constructor passed to New becomes the outermost handler in the resulting call chain, with a nil handler treated as http.DefaultServeMux. Append and Extend never mutate the receiver — each allocates a new backing slice and copies both sets of constructors into it, so chains can be shared as a common base and branched without aliasing bugs. There is no dependency injection, no reflection-based dispatch, and no internal state beyond the constructor slice — the abstraction really is just a for loop, as the README itself claims.

Tech Stack The module is github.com/justinas/alice, declaring a go 1.12 directive, with zero external dependencies — the only import anywhere in the package is the standard library’s net/http. Build tooling is minimal: a .travis.yml runs go test across an extensive matrix of historical Go versions and architectures, there is no go.sum (nothing third-party to lock), no Makefile, no linter configuration, and no Dockerfile. This is a bare, importable library rather than a deployable service, with a tech stack that begins and ends at the standard net/http package.

Code Quality chain_test.go provides straightforward unit tests using the standard testing package and httptest, exercising New, Then, ThenFunc, Append, and Extend, including explicit immutability checks (asserting the underlying constructor slices are distinct after Append/Extend) and correct execution order via a tagMiddleware helper that writes an identifying tag into the response body. Error handling is minimal because there is little that can fail: the only defensive behavior is treating a nil http.Handler as http.DefaultServeMux. Naming is idiomatic Go — short receivers, Then/ThenFunc mirroring the standard library’s Handler/HandlerFunc pattern — and every exported symbol carries a doc comment with runnable-looking usage examples. CI relies on an aging Travis configuration testing across many Go versions rather than any modern linting gate, and no linter/formatter configuration is checked into the repo.

API Design Alice’s public surface is deliberately tiny: one type and four methods, chosen to mirror stdlib naming (Then/ThenFunc echoing Handler/HandlerFunc) so the learning curve is close to zero for anyone already comfortable with net/http. Getting started requires no boilerplate: any middleware written in the ordinary Go decorator style already satisfies Constructor, so alice.New(mw1, mw2).Then(handler) works immediately with existing code, unlike frameworks that require adopting a bespoke middleware type. Doc comments illustrate reuse patterns — shared base chains, Append for route-specific extensions — directly inline, and dropping Alice later for manual nesting or another chaining library requires no changes to the middleware functions themselves. The tradeoff for this minimalism is a total absence of extras: no per-route metadata, no built-in logging/recovery middleware, and no context-based short-circuiting API, all of which must come from user-supplied middleware or another package.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search