Chaining middleware in Go using justinas/alice package
Tags: go, middleware
Published: 2021-07-27 21:05:59

Following on from https://dev.to/iamelesq/custom-middleware-basics-with-go-no-frameworks-29eg

We can make a couple of improvements to the simple API we created that included a couple of middleware wrapping functions. 

1. We're going to split the project into separate files. 
2. We're going to use a 3rd party package to chain the middleware functions using a popular community package called alice. 

## Let's go

1. create a new project and run `go mid init <name-of-your-project>`
2. Run `go get github.com/justinas/alice` on the cli. 
3. Create `main.go`, `handlers.go` & `middlewares.go` files. 

### add our handler 

In the handlers.go file we want to place the following code. 

```go 
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
)

type Lesson struct {
	Title   string
	Summary string
}

func handle(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodPost {

		// create a variable of our defined struct type Lesson
		var tempLesson Lesson

		decoder := json.NewDecoder(r.Body)
		err := decoder.Decode(&tempLesson)
		if err != nil {
			panic(err)
		}

		defer r.Body.Close()

		fmt.Printf("Title: %s. Summary: %s\n", tempLesson.Title, tempLesson.Summary)
		w.WriteHeader(http.StatusCreated)
		w.Write([]byte("201 - Created"))

	} else {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("405 - Method Not Allowed"))
	}
}
```

### Add the middleware functions

In the middlewares.go file we can add the following code.

```go
package main

import (
	"log"
	"net/http"
	"strconv"
	"time"
)

func filterContentType(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Println("Inside filterContentType middleware, before the request is handled")

		// filter requests by mime type
		if r.Header.Get("Content-Type") != "application/json" {
			w.WriteHeader(http.StatusUnsupportedMediaType)
			w.Write([]byte("415 - Unsupported Media Type. JSON type expected"))
			return
		}

		// handle the request
		handler.ServeHTTP(w, r)

		log.Println("Inside filterContentType middleware, after the request was handled")
	})
}

func setServerTimeCookie(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Println("Inside setServerTimeCookie middleware, before the request is handled")

		// handles the request
		handler.ServeHTTP(w, r)

		cookie := http.Cookie{Name: "Server Time(UTC)", Value: strconv.FormatInt(time.Now().Unix(), 10)}
		http.SetCookie(w, &cookie)

		log.Println("Inside setServerTimeCookie middleware, after the request is handled")
	})
}
```

### add the main function

in main.go we need to add the following

```go
package main

// chaing middleware with alice.
// here we will use the alice package to demo chainging middleware
// around our original or main handler.

import (
	"log"
	"net/http"

	"github.com/justinas/alice"
)

func main() {

	port := ":8080"

	originalHandler := http.HandlerFunc(handle)
	chain := alice.New(filterContentType, setServerTimeCookie).Then(originalHandler)

	http.Handle("/lesson", chain)
	log.Fatal(http.ListenAndServe(port, nil))
}
```

As you can see our refactor has added a 3rd party dependency but increased readbility. The fluent API of the `alice` package makes for clear and readable code when used. 

Splitting the project into multiple files here is trivial as we have a tiny codebase and it is purely for demo purposes, however it is reasonably good, common practice to split your files in Go projects. This lowers the cognitive load from each file on the reader.