Skip to content

Middlewares ​

A middleware is code that runs around a handler: before it, after it, or instead of it. Logging every request, checking an API key and limiting the request rate are all middlewares.

What you'll learn

  • The two kinds of middleware: functions and components
  • The three places a middleware can run: the whole app, a preá or a route
  • The order in which everything runs

A middleware ​

go
func Timing(c *gin.Context) {
	start := time.Now()   // before the handler

	c.Next()              // run the rest: other middlewares, then the handler

	logger.Info("took", "path", c.Request.URL.Path, "time", time.Since(start)) // after
}

c.Next() continues the chain. To stop it, for example because a check failed, reply and call c.Abort():

go
func (m *APIKey) Handle(c *gin.Context) {
	if m.Key == "" || c.Request.Method == http.MethodGet || c.Request.Method == http.MethodHead {
		c.Next()
		return
	}
	if subtle.ConstantTimeCompare([]byte(c.GetHeader("X-API-Key")), []byte(m.Key)) != 1 {
		core.Error(c, core.NewError(http.StatusUnauthorized, "missing or wrong X-API-Key"))
		c.Abort()
		return
	}
	c.Next()
}

Two kinds ​

Functions are the simplest kind: a func(c *gin.Context), also known as core.Middleware. They can't ask for components.

bash
prea g m timing --func

Components are structs with a Handle(c *gin.Context) method. Preá creates them like any other component, so they can use inject:"" and value:"", and have Init and Close:

bash
prea g m api_key
go
// APIKey protects the writes: POST, PATCH and DELETE need the X-API-Key
// header when todo.api_key is set. Reads stay open.
type APIKey struct {
	Key string `value:"todo.api_key"`
}

var _ core.MiddlewareComponent = (*APIKey)(nil)

Pick a function when the middleware needs nothing, and a component when it needs a service, the database or a setting.

Three places ​

The whole app ​

Middlewares in preas.Middlewares run on every request:

go
// Middlewares run on every request, after Security.
var Middlewares = []any{
	core.RequestLogger,
	core.Recovery,
	middleware.APIKey{},
}

A component here can ask for the globals and for anything any preá exports.

One preá ​

Middlewares in a module's Middlewares run on every route of that preá:

go
var Module = &core.Module{
	Name:        "admin",
	Providers:   []any{Handler{}},
	Middlewares: []any{middleware.Timing, middleware.AdminOnly{}},
}

A component listed here is created for the preá automatically. You don't need to repeat it in Providers.

One route ​

Put the middleware before the handler, in the route itself:

go
g.GET("/report", middleware.Timing, h.report)

For a component, declare it in Providers, ask for it in the handler and use its Handle:

go
var Module = &core.Module{
	Name:      "report",
	Providers: []any{middleware.AdminOnly{}, Handler{}},
}

type Handler struct {
	AdminOnly *middleware.AdminOnly `inject:""`
}

func (h *Handler) Register(r gin.IRouter) {
	r.GET("/report", h.AdminOnly.Handle, h.report)
}

Always let Preá create components

(&middleware.AdminOnly{}).Handle compiles, but that instance never had its fields filled, and it panics on the first request. Pass middleware.AdminOnly{} to a list, or inject it.

The order ​

For each request, the chain runs in this order:

security  →  app middlewares  →  preá middlewares  →  route middlewares  →  handler

Inside each list, middlewares run in the order you wrote them. HTTP security runs first, so a blocked request never reaches your code.

Next steps ​

Released under the MIT License.