Golang Web Framework Comparison
This suite aims to compare the public API of various Go web frameworks and routers.
NOTE While code blocks are self-explained the list of PROs an CONs are highly opinionated and targeted Go 1.7+. Even if some frameworks has a more
Contents
- Reviewed libraries and frameworks
- HTTP handler. Signature
- HTTP middleware. Signature and sample code
- HTTP handler. Write Go struct as JSON response
- HTTP handler. Bind JSON payload into Go struct
Reviewed libraries and frameworks
- stdlib net/http
- gin-gonic/gin
- go-macaron/macaron
- go-martini/martini
- go-ozzo/ozzo-routing
- gocraft/web
- goji/goji
- gorilla/mux
- hoisie/web
- julienschmidt/httprouter (router)
- labstack/echo
- mholt/binding (request binding)
- pressly/chi
- TODO astaxie/beego
- TODO revel/revel
- unrolled/render (response rendering)
- urfave/negroni
- zenazn/goji
HTTP handler. Signature
👎 ❗ revel, beego are not idiomatic Go because they forces you to embed handler to the Framework's struct👎 martini, hoisie/web, macaron handlers are not strongly typed due to reflective dependency injection (which leads to poor performance)👎 zenazn/goji handler and middleware is not strongly typed to emulate function overload👎 gocraft/web handler is not strongly typed because it's a method with pointer receiver that could be any user-defined struct- 👎 negroni, stdlib net/http does not dispatch request by HTTP verb (more boilerplate code)
👎 hoisie/web, zenazn/goji offers to use singletone istance of the server struct which is quite bad practice- ❓ goji/goji has quite unusual API to dispatch requests by HTTP verb but it's still more verbose than in echo, gin, julienschmidt/httprouter
👍 echo, gin, julienschmidt/httprouter, zenazn/goji, goji/goji, ozzo-routing, pressly/chi handers are stronly typed👍 echo, gin, julienschmidt/httprouter, zenazn/goji, ozzo-routing, pressly/chi do dispatch requests by HTTP verb👍 echo, gin, zenazn/goji, ozzo-routing, pressly/chi support HTTP middleware- 👍 echo, ozzo-routing handers returns error value which could be handled in next middlewares in the chain
❓ julienschmidt/httprouter does not support HTTP middleware, gorilla/handlers are recommended instead❓ goji/goji keeps handler interface standard but it's quite verbose to type- FYI labstack/echo has own router, supports most handler / middleware APIs
- FYI gin uses julienschmidt/httprouter, per-request context map
- FYI negroni recommends gorilla/mux router; Golang 1.7 context can be used (or gorilla/context for Golang < 1.7)
👎 gorilla/context uses global context map which may lead to lock contention👍 Golang 1.7 context uses per-request context map, Request.WithContext does a shallow copy of *Request
stdlib net/http
stdlib net/http or negroni + stdlib net/http or negroni + gorilla/mux, https://golang.org/pkg/net/http/#ServeMux.HandleFunc
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { ...
gin-gonic/gin
https://godoc.org/github.com/gin-gonic/gin#RouterGroup.GET
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes
type HandlerFunc func(*gin.Context)
g.GET("/", func(c *gin.Context) { ...
go-macaron/macaron
https://godoc.org/github.com/go-macaron/macaron#Router.Get
func (r *Router) Get(pattern string, h ...Handler) (leaf *Route)
type Handler interface{}
m.Get("/", func(w http.ResponseWriter, req *http.Request, log *log.Logger) { ...
m.Get("/", func(ctx *macaron.Context) (int, []byte) { ...
go-martini/martini
https://godoc.org/github.com/go-martini/martini#Router
func (r *Router) Get(string, ...Handler) Route
type Handler interface{}
m.Get("/", func(w http.ResponseWriter, r *http.Request, u *SomeUserService) { ...
m.Get("/", func() (int, string) { ...
gocraft/web
https://godoc.org/github.com/gocraft/web#Router.Get
func (r *Router) Get(path string, fn interface{}) *Router
w.Get("/", func (c *SomeUserContext) SayHello(w web.ResponseWriter, r *web.Request) { ...
goji/goji
https://godoc.org/github.com/goji/goji#Mux.HandleFunc
func (m *Mux) HandleFunc(p Pattern, h func(http.ResponseWriter, *http.Request))
func (m *Mux) HandleFuncC(p Pattern, h func(context.Context, http.ResponseWriter, *http.Request))
type Pattern interface {
Match(context.Context, *http.Request) context.Context
}
g.HandleFunc(pat.Get("/"), func (w http.ResponseWriter, r *http.Request) { ...
g.HandleFuncC(pat.Get("/"), func (ctx context.Context, w http.ResponseWriter, r *http.Request) { ...
go-ozzo/ozzo-routing
https://godoc.org/github.com/go-ozzo/ozzo-routing#RouteGroup.Get
func (r *RouteGroup) Get(path string, handlers ...Handler) *Route
type Handler func(*routing.Context) error
r.Get("/", func(c *routing.Context) error { ...
hoisie/web
https://godoc.org/github.com/hoisie/web#Get
func Get(route string, handler interface{})
w.Get("/", func(ctx *web.Context, val string) { ...
w.Get("/", func(val string) string { ...
julienschmidt/httprouter
https://godoc.org/github.com/julienschmidt/httprouter#Router.GET
func (r *Router) GET(path string, handle Handle)
type Handle func(http.ResponseWriter, *http.Request, Params)
r.GET("/", func Index(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { ...
labstack/echo
https://godoc.org/github.com/labstack/echo#Echo.Get
func (e *Echo) Get(path string, h HandlerFunc, m ...MiddlewareFunc)
type HandlerFunc func(echo.Context) error
type MiddlewareFunc func(HandlerFunc) HandlerFunc
e.Get("/", func(c echo.Context) error { ...
pressly/chi
https://godoc.org/github.com/pressly/chi#Mux.Get
func (mx *Mux) Get(pattern string, handlerFn http.HandlerFunc)
c.Get("/", func(w http.ResponseWriter, r *http.Request) { ...
zenazn/goji
https://godoc.org/github.com/zenazn/goji#Get
func Get(pattern web.PatternType, handler web.HandlerType)
type PatternType interface{}
type HandlerType interface{}
g.Get("/", func(w http.ResponseWriter, req *http.Request) { ...
HTTP middleware. Signature and sample code
TODO add godoc urls
- 👎 revel, beego are not considered, they forces you to embed handler to the Framework's struct
👎 martini, hoisie/web, macaron are not considered, their handers are not strongly typed due to reflective dependency injection👎 zenazn/goji handler and middleware are not strongly typed to emulate function overload- 👎 gin has "func (c *Context) Next()" function that visible to all handlers but must be called only inside middleware
- 👍 echo, goji/goji, ozzo-routing, pressly/chi, negroni has strongly typed middleware with reasonable signatures
❓ negroni uses quite unusual signature for middleware. Why? I have only one explanation at this point. Author decide to avoid usage of higher-order function, so it would be easier to grasp for not-experienced developers- TODO gocraft/web PROs and CONs that related to the middleware API
stdlib net/http, gorilla/mux, julienschmidt/httprouter, pressly/chi
func(http.Handler) http.Handler
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do some stuff before
next.ServeHTTP(w, r)
// do some stuff after
})
}
gin-gonic/gin
// gin uses same signature for both handler and middleware
type HandlerFunc func(*Context)
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
// do some stuff before
c.Next()
// do some stuff after
}
}
gocraft/web
// TODO
func (c *SomeUserContext) SetHelloCount(w web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
// do some stuff before
next(w, r)
// do some stuff after
}
goji/goji
func(http.Handler) http.Handler // standard middleware
func(goji.Handler) goji.Handler // context-aware middleware
func Middleware(inner goji.Handler) goji.Handler {
return goji.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
// do some stuff before
inner.ServeHTTPC(ctx, w, r)
// do some stuff after
})
}
go-ozzo/ozzo-routing
// middlewares and handers share the same type "Handler" in ozzo-routing
func Middleware(c *routing.Context) error {
// do some stuff before
err := c.Next()
// do some stuff after
return err
}
labstack/echo
type MiddlewareFunc func(HandlerFunc) HandlerFunc
type HandlerFunc func(Context) error
func Middleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// do some stuff before
err := next(c)
// do some stuff after
return err
}
}
urfave/negroni
type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
func Middleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
// do some stuff before
next(rw, r)
// do some stuff after
}
HTTP handler. Write Go struct as JSON response
Common part
type Greeting struct {
Hello string `json:"hello"`
}
stdlib net/http, gorilla/mux, negroni, zenazn/goji, goji/goji, julienschmidt/httprouter. V1 - use only stdlib
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(Greeting{Hello: "world"}); err != nil {
panic(err)
}
})
stdlib net/http, gorilla/mux, negroni, zenazn/goji, goji/goji, julienschmidt/httprouter. V2 - use unrolled/render that mentioned in negroni's README
r := render.New()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
if err := r.JSON(w, http.StatusOK, Greeting{Hello: "world"}); err != nil {
panic(err)
}
})
gin-gonic/gin
r.GET("/", func(c *gin.Context) {
// method will panic if serialization fails
c.JSON(http.StatusOK, Greeting{Hello: "world"})
})
go-macaron/macaron. Use either macaron.Context or macaron.render.Render
// render.Render or macaron.Context will be passed to the handler by using dependency injection
m.Get("/", func(r render.Render) {
// method will render HTTP 500 response if serialization fails
r.JSON(http.StatusOK, Greeting{Hello: "world"})
})
// V2
m.Get("/", func(ctx *macaron.Context) {
ctx.JSON(http.StatusOK, Greeting{Hello: "world"})
}
go-martini/martini + martini-contrib/render that menioned in README
// render.Render will be passed to the handler by using dependency injection
m.Get("/", func(r render.Render) {
// method will render HTTP 500 response if serialization fails
r.JSON(http.StatusOK, Greeting{Hello: "world"})
})
go-ozzo/ozzo-routing
// Context.Write will write the data in the format determined by the content negotiation middleware (included)
r.Get("/", func(c *routing.Context) error {
// if Write returns an error, the framework will render HTTP 500 response by default
return c.Write(Greeting{Hello: "world"})
})
gocraft/web + corneldamian/json-binding than mentioned in README
TODO Review implementation in https://github.com/corneldamian/json-binding/blob/master/response.go#L47
hoisie/web
// web.Context will be passed to the handler by using dependency injection
w.Get("/", func(ctx *web.Context) string {
ctx.ContentType("json")
data, err := json.Marshal(Greeting{Hello: "world"})
if err != nil {
panic(err)
}
return string(data)
})
// V2
w.Get("/", func(ctx *Context) []byte {
ctx.ContentType("json")
data, err := json.Marshal(Greeting{Hello: "world"})
if err != nil {
panic(err)
}
return data
})
labstack/echo
e.Get("/", func(c echo.Context) error {
// method will return error if serialization fails, then handler return that error to the middleware/framework
return c.JSON(http.StatusOK, Greeting{Hello: "world"})
})
pressly/chi
c.Get("/", func(w http.ResponseWriter, r *http.Request) {
// method will render HTTP 500 response if serialization fails
render.JSON(w, r, Greeting{Hello: "world"})
})
HTTP handler. Bind JSON payload into Go struct
Common part
type Greeting struct {
Hello string `json:"hello"`
}
stdlib net/http, gorilla/mux, negroni, zenazn/goji, goji/goji, julienschmidt/httprouter, go-macaron/macaron, hoisie/web. V1 - use only stdlib
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
g := new(Greeting)
defer r.Body.Close()
if err := json.NewDecoder(r.Body).Decode(g); err != nil {
panic(err)
}
// TODO put here validation code
w.WriteHeader(http.StatusCreated)
}
stdlib net/http, gorilla/mux, negroni, zenazn/goji, goji/goji, julienschmidt/httprouter, go-macaron/macaron, hoisie/web. V2 - use mholt/binding
// mholt/binding is the reflectioness data binding library developed by co-author of the martini-contrib/binding
// for JSON you can return an empty field map
func (g *Greeting) FieldMap() binding.FieldMap {
return binding.FieldMap{}
}
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
g := new(Greeting)
errs := binding.Bind(r, g)
// If parsing failed Handle will render HTTP 400 response with error details and return true
if errs.Handle(w) {
return
}
w.WriteHeader(http.StatusCreated)
}
gin-gonic/gin
r.POST("/", func(c *gin.Context) {
g := new(Greeting)
if c.BindJSON(g); err != nil {
panic(err)
}
c.Abort(http.StatusCreated)
})
go-martini/martini + martini-contrib/binding that menioned in README
// Greeting struct may also contain binding tags
type Greeting struct {
Hello string `json:"hello" binding:"required"`
}
// TODO check is binding.Bind(..) renders HTTP 400 response if validation fails
m.Post("/", binding.Bind(Greeting{}), func(g Greeting) (int, string) {
return http.StatusNoContent, ""
})
go-ozzo/ozzo-routing
r.Post("/", func(c *routing.Context) error {
var g Greeting
// Context.Read will read the data in the format specified by the "Content-Type" header
if err := c.Read(&g); err != nil {
return err
}
c.Response.WriteHeader(http.StatusCreated)
return nil
})
gocraft/web + corneldamian/json-binding than mentioned in README
TODO Review implementation in https://github.com/corneldamian/json-binding/blob/master/request.go#L56
labstack/echo
e.POST("/", func(c echo.Context) error {
g := new(Greeting)
if err := c.Bind(g); err != nil {
return err
}
return c.NoContent(http.StatusCreated)
})
pressly/chi
c.Post("/", func(w http.ResponseWriter, r *http.Request) {
g := new(Greeting)
if err := render.Bind(r.Body, g); err != nil {
panic(err)
}
render.NoContent(w, r)
})