Let's Go Middleware › Composable middleware chains
Previous · Contents · Next
Chapter 6.5.

Composable middleware chains

In this chapter I’d like to introduce the justinas/alice package to help us manage our middleware/handler chains.

You don’t need to use this package, but the reason I recommend it is because it makes it easy to create composable, reusable, middleware chains — and that can be a real help as your application grows and your routes become more complex. The package itself is also small and lightweight, and the code is clear and well written.

To demonstrate its features in one example, it allows you to rewrite a handler chain from this:

return myMiddleware1(myMiddleware2(myMiddleware3(myHandler)))

Into this, which is a bit clearer to understand at a glance:

return alice.New(myMiddleware1, myMiddleware2, myMiddleware3).Then(myHandler)

But the real power lies in the fact that you can use it to create middleware chains that can be assigned to variables, appended to, and reused. For example:

myChain := alice.New(myMiddlewareOne, myMiddlewareTwo)
myOtherChain := myChain.Append(myMiddleware3)
return myOtherChain.Then(myHandler)

If you’re following along, please install the justinas/alice package using go get:

$ go get github.com/justinas/alice@v1
go: downloading github.com/justinas/alice v1.2.0

And if you open the go.mod file for your project, you should see a new corresponding require statement, like so:

File: go.mod
module snippetbox.alexedwards.net

go 1.23.0

require github.com/go-sql-driver/mysql v1.8.1

require (
    filippo.io/edwards25519 v1.1.0 // indirect
    github.com/justinas/alice v1.2.0 // indirect
)

Again, this is currently listed as an indirect dependency because we’re not actually importing and using it in our code yet.

Let’s go ahead and do that now, updating our routes.go file to use the justinas/alice package as follows:

File: cmd/web/routes.go
package main

import (
    "net/http"

    "github.com/justinas/alice" // New import
)

func (app *application) routes() http.Handler {
    mux := http.NewServeMux()

    fileServer := http.FileServer(http.Dir("./ui/static/"))
    mux.Handle("GET /static/", http.StripPrefix("/static", fileServer))
   
    mux.HandleFunc("GET /{$}", app.home)
    mux.HandleFunc("GET /snippet/view/{id}", app.snippetView)
    mux.HandleFunc("GET /snippet/create", app.snippetCreate)
    mux.HandleFunc("POST /snippet/create", app.snippetCreatePost)

    // Create a middleware chain containing our 'standard' middleware
    // which will be used for every request our application receives.
    standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders)

    // Return the 'standard' middleware chain followed by the servemux.
    return standard.Then(mux)
}

If you want, feel free to restart the application at this point. You should find that everything compiles correctly and the application continues to work in the same way as before. You can also run go mod tidy again to remove the // indirect annotation from the go.mod file.