Improve this page

Middleware

Middleware allows for the interjection of code in the request/response cycle. Common use cases for middleware are things like logging (which Buffalo already does), authentication requests, etc.

A list of "known" middleware packages can be found at https://toolkit.gobuffalo.io/tools?topic=middleware.

The Middleware Interface

The buffalo.MiddlewareFunc interface is any function that takes a buffalo.Handler and returns a buffalo.Handler.

func MyMiddleware(next buffalo.Handler) buffalo.Handler {
  return func(c buffalo.Context) error {
    // do some work before calling the next handler
    err := next(c)
    // do some work after calling the next handler
    return err
  }
}

By implementing the buffalo.MiddlewareFunc interface you are able to control the flow of execution in your application, think an authorization middleware; send errors off to your favorite monitoring tool; load data on to the buffalo.Context, and more.

Using Middleware

a := buffalo.New(buffalo.Options{})
a.Use(MyMiddleware)
a.Use(AnotherPieceOfMiddleware)

In the above example all requests will first go through the MyMiddleware middleware, and then through the AnotherPieceOfMiddleware middleware before first getting to their final handler.

NOTE: Middleware defined on an application is automatically inherited by all routes and groups in that application.

Using Middleware with One Action

Often there are cases when you want to use a piece of middleware on just one action, and not on the whole application or resource.

Since the definition of a piece of middleware is that it takes in a buffalo.Handler and returns a buffalo.Handler you can wrap any buffalo.Handler in a piece of middlware.

a := buffalo.New(buffalo.Options{})
a.GET("/foo", MyMiddleware(MyHandler))

This does not affect the rest of the middleware stack that is already in place, instead it appends to the middleware chain for just that one action.

This can be taken a step further, by wrapping unlimited numbers of middleware around a buffalo.Handler.

a := buffalo.New(buffalo.Options{})
a.GET("/foo", MyMiddleware(AnotherPieceOfMiddleware(MyHandler)))

Group Middleware

a := buffalo.New(buffalo.Options{})
a.Use(MyMiddleware)
a.Use(AnotherPieceOfMiddleware)

g := a.Group("/api")
// authorize the API end-point
g.Use(AuthorizeAPIMiddleware)
g.GET("/users", UsersHandler)

a.GET("/foo", FooHandler)

In the above example the MyMiddleware and AnotherPieceOfMiddleware middlewares will be called on all requests, but the AuthorizeAPIMiddleware middleware will only be called on the /api/* routes.

GET /foo -> MyMiddleware -> AnotherPieceOfMiddleware -> FooHandler
GET /api/users -> MyMiddleware -> AnotherPieceOfMiddleware -> AuthorizeAPIMiddleware -> UsersHandler

Skipping Middleware

There are times when, in an application, you want to add middleware to the entire application, or a group, but not call that middleware on a few individual handlers. Buffalo allows you to create these sorts of mappings.

// actions/app.go
a := buffalo.New(buffalo.Options{})
a.Use(AuthorizeUser)

// skip the AuthorizeUser middleware for the NewUser and CreateUser handlers.
a.Middleware.Skip(AuthorizeUser, NewUser, CreateUser)

a.GET("/users/new", NewUser)
a.POST("/users", CreateUser)
a.GET("/users", ListUsers)
a.GET("/users/{id}", ShowUser)
// OUTPUT
GET /users/new -> NewUser
POST /users -> CreateUser
GET /users -> AuthorizeUser -> ListUsers
GET /users/{id} -> AuthorizeUser -> ShowUser


IMPORTANT: The middleware function and the action functions you want to skip MUST be the same Go instance.
// EXAMPLE 1
m1 := MyMiddleware()
m2 := MyMiddleware()

app.Skip(m2, Foo, Bar) // WON'T WORK m2 != m1
app.Skip(m1, Foo, Bar) // WORKS
// EXAMPLE 2
app.Resource("/widgets", WidgetResource{})
app.Skip(mw, WidgetResource{}.Show) // WON'T WORK

wr := WidgetResource{}
app.Resource("/widgets", wr)
app.Skip(mw, wr.Show) // WORKS

See https://godoc.org/github.com/gobuffalo/buffalo#MiddlewareStack.Skip for more details on the Skip function.

Skipping Resource Actions

Often it is necessary to want to skip middleware for one or more actions. For example, allowing guest users to view the List and Show actions on a resource, but requiring authorization on the rest of the actions.

Understanding from the Skipping Middleware section we need to make sure that we are using the same functions when we register the resource as we do when we want to skip the middleware on those functions later.

The line that was generated in actions/app.go by buffalo generate resource will need to be changed to accommodate this requirement.

// BEFORE
app.Resource("/widgets", WidgetResource{})
// AFTER
res := WidgetResource{}
wr := app.Resource("/widgets", res)
wr.Middleware.Skip(Authorize, res.Index, res.Show)

Clearing Middleware

Since middleware is inherited from its parent, there maybe times when it is necessary to start with a "blank" set of middleware.

// actions/app.go
a := buffalo.New(buffalo.Options{})
a.Use(MyMiddleware)
a.Use(AnotherPieceOfMiddleware)

g := a.Group("/api")
// clear out any previously defined middleware
g.Middleware.Clear()
g.Use(AuthorizeAPIMiddleware)
g.GET("/users", UsersHandler)

a.GET("/foo", FooHandler)
// OUTPUT
GET /foo -> MyMiddleware -> AnotherPieceOfMiddleware -> FooHandler
GET /api/users -> AuthorizeAPIMiddleware -> UsersHandler

Listing an Application's Middleware

To get a complete list of the middleware your application is using, broken down by grouping, can be found by running the buffalo task middleware command.

$ buffalo t middleware

-> /
github.com/gobuffalo/buffalo.*App.defaultErrorMiddleware
github.com/gobuffalo/buffalo.*App.PanicHandler
github.com/gobuffalo/buffalo.RequestLoggerFunc
github.com/gobuffalo/buffalo.sessionSaver
github.com/gobuffalo/mw-forcessl.Middleware.func1
github.com/markbates/coke/actions.App.func1
github.com/markbates/coke/actions.trackLastURL
github.com/markbates/coke/actions.TrackingCookie
github.com/markbates/coke/actions.App.func3
github.com/gobuffalo/mw-paramlogger.ParameterLogger
github.com/gobuffalo/mw-csrf.glob..func1
github.com/gobuffalo/buffalo-pop/pop/popmw.Transaction.func2
github.com/markbates/coke/actions.SetCurrentUser
github.com/markbates/coke/actions.SetPageTitle
-> /courses/{course_slug}
github.com/gobuffalo/buffalo.*App.defaultErrorMiddleware
github.com/gobuffalo/buffalo.*App.PanicHandler
github.com/gobuffalo/buffalo.RequestLoggerFunc
github.com/gobuffalo/buffalo.sessionSaver
github.com/gobuffalo/mw-forcessl.Middleware.func1
github.com/markbates/coke/actions.App.func1
github.com/markbates/coke/actions.trackLastURL
github.com/markbates/coke/actions.TrackingCookie
github.com/markbates/coke/actions.App.func3
github.com/gobuffalo/mw-paramlogger.ParameterLogger
github.com/gobuffalo/mw-csrf.glob..func1
github.com/gobuffalo/buffalo-pop/pop/popmw.Transaction.func2
github.com/markbates/coke/actions.SetCurrentUser
github.com/markbates/coke/actions.SetPageTitle
github.com/markbates/coke/actions.FindCourse
-> /admin
github.com/gobuffalo/buffalo.*App.defaultErrorMiddleware
github.com/gobuffalo/buffalo.*App.PanicHandler
github.com/gobuffalo/buffalo.RequestLoggerFunc
github.com/gobuffalo/buffalo.sessionSaver
github.com/gobuffalo/mw-forcessl.Middleware.func1
github.com/markbates/coke/actions.App.func1
github.com/markbates/coke/actions.trackLastURL
github.com/markbates/coke/actions.TrackingCookie
github.com/markbates/coke/actions.App.func3
github.com/gobuffalo/mw-paramlogger.ParameterLogger
github.com/gobuffalo/mw-csrf.glob..func1
github.com/gobuffalo/buffalo-pop/pop/popmw.Transaction.func2
github.com/markbates/coke/actions.SetCurrentUser
github.com/markbates/coke/actions.SetPageTitle
github.com/markbates/coke/actions.Authorize
github.com/markbates/coke/actions.AuthorizeAdmin