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:
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:
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.