Using Middleware In Go The Right Way

itachi sasuke
itachi sasuke
i.
Aug 24, 2018 6 min read

Middlewares are an essential part of each and every api or web application.Go makes this an easy and painless process to integrate a middleware in your application.

You can opt for third party libraries such as negroni or Alice for writing your middleware but why add extra dependency to your application while middleware are so easy to write with the standard library?In this tutorial we are going to write a middleware for a simple api that we will write.You can get the full code from here.

Making a simple go rest api

We are going to use the api we wrote in the last tutorial.If you have not yet looked at it yet here it is how to deploy golang to prodution.For the sake of conveniency am going to repeat the same api here.

If you already have an existing golang api,feel free to skip to the making the middleware section.

This will be our project structure.

1
2
3
4
5
6
7
  main.go
  middlewares
     middleware.go
  models
     models.go
  handlers
     handlers.go

The handlers.go- has all the handlers(controllers) we want to hook to our routes

The models.go-This contains all our model definition which in this case is  our job model.

The Main.go – This is our entry point where we shall serve our application from.

middleware.go-We are going to define all our middleware in this  file.

Defining our model

We are going to have one model for the sake of simplicity.If your application has many models you should define them here.In practice if you have a big and complex project you define models in related packages .

1
2
3
4
5
package models
type Job struct {
	ID uint `json:"id"`
	Name string `json:"name"`
	

 

Simple handler to serve our jobs

Now that we have our model ready it time to write a simple handler to serve our jobs.Every request we send  requesting for jobs will be servered by this method.We will later hook it with a route in the main package.

The handler will return a json response which we can consume.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package handlers
import (
   "net/http"
   "encoding/json"
   "middware-go/models"
)
//A handler to fetch all the jobs
func GetJobs(w http.ResponseWriter, r *http.Request) {
   //make a slice to hold our jobs data
   var jobs []models.Job
   ///add some job to the slice
   jobs = append(jobs, models.Job{ID: 1, Name: "Accounting"})
   jobs = append(jobs, models.Job{ID: 2, Name: "Programming"})
   json.NewEncoder(w).Encode(jobs)
}

Serving our jobs

In this tutorial i am going to use the  Chi router to handle my routing but feel free to use any router available.

Now copy the following code in your main.go.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main
import (
   "net/http"
   "middware-go/handlers"
   	"log"
   	"github.com/go-chi/chi"
)
func main() {
	router := chi.NewRouter()
	router.Get("/api/jobs", handlers.GetJobs)
	//run it on port 8080
	err := http.ListenAndServe("0.0.0.0:8080", router)
	if err != nil {
		log.Fatal(err)
	}
}

Our api is now complete.You can test it by running.

go run main.go

Making a middleware in go

Lets define middleware that logs the time the request was sent.

Lets start by writing the code and i will later explain what is happening.

Copy this in the middleware.go file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package middleware
import (
    "log"
    "time"
)
//we define a function takes in a http.Handler  and return a http.Handler
type Middleware func( http.Handler) http.Handler
func LogTime() Middleware {
	return func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                        log.Println("This request was sent at",time.Now())
			h.ServeHTTP(w, r)
		})
	}
}

The  reason we have defined our middleware function that takes in a http.Handler and return a http.Handler is to allow us to chain multiple middlewares.

The LogTime middleware logs the time a request was sent to our server.If for example you wanted to check for a user token in an api protected with jwt you would use the same format.

Using our middleware

The best way to use middlewares i golang is writing a simple function that will help to adapt our handler with all the middleware it requires.

The function should take in our handler(that is the GetJob) and a slice of all middleware we want to use with GetJobs.

Copy this code to in the middleware.go.

1
2
3
4
5
6
func Adapt(h http.Handler, adapters ...Middleware) http.Handler {
	for _, adapter := range adapters {
		h = adapter(h)
	}
	return h
}

What happens here is that we pass a handler e.g(The GetJobs)and a slice of all the middlewares we want that handler to use.Then the adapted  handler is returned.

Modify the main.go to look like below .So that our (GetJobs handler can use our middleware).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import (

	"github.com/go-zoo/bone"
	"net/http"
	"middware-go/handlers"
	"middware-go/middlewares"
)

func main() {

	router := bone.New()


	router.Get("/api/jobs", middleware.Adapt(http.HandlerFunc(handlers.GetJobs), middleware.LogTime()))
	//run it on port 8080
	http.ListenAndServe("0.0.0.0:8080", router)


}

Every time you send a request to  the (api/jobs) it should log the time the request  was sent.

As you may have noticed sometimes an endpoint may need to use more than one middleware.With the approach we have taken this will be very easier as our  adapt function accepts a slice  of middlewares.Lets try this with two middlewares.

Chaining Multiple Middlewares in go

Imagine you want to print some text every time you receive a request .Lets say something like.”I like chocolate”.You can make a middleware that does this.Note:I am using fairly simple middlewares to make it as simple as possible to understand.For a real world project your will be making middlewares to do work like checking for a user jwt token.However the implementation is the same.

Copy the code below in your middleware.go.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func SecondMiddleware() Middleware {
	return func(h http.Handler) http.Handler {


		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			log.Print("I like chocolate")

			h.ServeHTTP(w, r)
		})
	}
}

Finally update the main.go to let our  GetJobs handler use both middlewares.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (

	"github.com/go-zoo/bone"
	"net/http"
	"middware-go/handlers"
	"middware-go/middlewares"
)

func main() {

	router := bone.New()


	router.Get("/api/jobs", middleware.Adapt(http.HandlerFunc(handlers.GetJobs), middleware.SecondMiddleware(),middleware.LogTime()))
	//run it on port 8080
	http.ListenAndServe("0.0.0.0:8080", router)


}

At this point if you try visiting this endpoint you will realize our endpoint is now using both middlewares.

It print both the current time(middleware.Logtime) and ‘i like chocolate’ (second middleware).

golang middlewares

Using this approach you can chain as many middleware as you wish.

Conclusion

In this tutorial we have seen how easy it is to write and use middlewares in golang.You can also read some of our previous tutorials How to deploy golang apps or enroll for our free classes at codesahara.

You can get the whole code for this tutorial on github.

Happy coding.

comments powered by Disqus