In a web server, a middleware is a function that sits between the incoming request and the final handler function. It takes the incoming request and performs some action on it before passing it along to the final handler function.

Some typical use cases for middleware include logging, authorization, and authentication, among others.

Middlewares can be (and often are) chained together. So, a logging middleware could pass a request to an authentication middle, which could then pass the request to the actual handler function.

Pattern

In Go, a middleware is just a function that takes an http.Handler as an argument and returns an http.Handler as a result, e.g.

func Middleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		//preprocessing logic here
 
		//call next handler in chain
		next.ServeHTTP(w, r)
 
		//any post-processing logic here
	})
}

An Example

Assume we have some custom Server struct that has the following fields:

type Server struct {
	Router *http.ServeMux
	Srvr *http.Server 
	Logger *slog.Logger //structured logger
}

And if we wanted to write some logging middleware that could use this Logger, we could write a method on a server like this:

func (s *Server) LoggingMiddleware(next http.Handler) http.Handler {
	  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	  start := time.Now()
 
	  next.ServeHTTP(w, r)
 
	  duration := time.Since(start)
 
	  s.Logger.Info("request received",
		  slog.Time("timestamp", time.Now()),
		  slog.String("method", r.Method),
		  slog.String("path", r.URL.Path),
		  slog.String("remote_addr", r.RemoteAddr),
		  slog.Duration("duration", duration)
	  )
  })
}

And then to use this on a given route, we wrap our HandlerFunc in the middleware:

s.Router.Handle("GET /", s.LoggingMiddleware(http.HandlerFunc(s.handleIndex))) //assumes we have some handleIndex method defined