Mixer: Classy HTTP Handlers in Go
Mixer is a small but powerful package for writing modular HTTP handlers in Go.
Mixer attempts to solve some of the problems that
Martini tried to solve, but with a
smaller, more idiomatic scope.
If you liked the idea of dependency injection in Martini, but you think it contained too much magic, then Mixer is a great fit.
Features
- Type safe
- Brutally simple to use
- Non intrusive design
- Zero external dependencies
- Compatible with most HTTP routers out there, choose your favorite!
- Dependency injection without reflection, type assertions, or other magic
Getting Started
Pull down this package with go get (go 1.18 or greater is required):
go get github.com/codegangsta/mixer
Hello world!
After installing your package you can create a simple http handler in Mixer:
package main
import (
"fmt"
"net/http"
"github.com/codegangsta/mixer"
)
func main() {
m := mixer.Classic()
hello := func(c mixer.Context) {
fmt.Fprint(c.ResponseWriter(), "Hello world")
}
http.ListenAndServe(":3000", m.Handler(hello))
}
This sure is simple, but it's not really useful. Once your http handlers start
growing, they are going to need to depend on things like databases, loggers,
sessions, users and all kinds of other functionality that mixer.Context
doesn't have. This is where the Custom Context comes in.
Adding your own context
Let's create an AppContext
struct to hold the context for our app. Beyond the basic stuff in mixer.Context
, it's also got a log.Logger
instance.
package main
import (
"fmt"
"log"
"net/http"
"os"
"github.com/codegangsta/mixer"
)
type AppContext struct {
mixer.Context
Logger *log.Logger
}
func main() {
m := mixer.New(func(c mixer.Context) *AppContext {
return &AppContext{
Context: c,
Logger: log.New(os.Stdout, "[mixer]", 0),
}
})
hello := func(c *AppContext) {
fmt.Fprint(c.ResponseWriter(), "Hello world")
}
http.ListenAndServe(":3000", m.Handler(hello))
}
Pretty cool right? This means we can easily add global and request scoped data
to our context struct, and the handlers will get them in a fully type-safe way.
No interface{}
, no reflection and no type assertions.
Adding Before and After Hooks
Mixer also supports Before
and After
hooks, in case you'd like to run some
logic or map some dependencies outside of the context func.
If we add a User
type to our AppContext
:
type AppContext struct {
mixer.Context
Logger *log.Logger
User *User
}
We can add that user in a Before
hook, maybe it comes from a cookie:
m.Before(func(c *AppContext) {
cookie, err := c.Request().Cookie("user-id")
if err != nil {
c.ResponseWriter().WriteHeader(500)
return
}
c.User = lookupUser(cookie.Value)
})
Same can be accomplished with an After
hook:
m.After(func(c *AppContext) {
c.Logger.Println("Request Finished")
})
FAQ
Is this a framework
No. This is not a web framework. I'd hardly even call it a package. Mixer is
more of a pattern for getting global and request scoped values to your handlers without much fuss.
Mixer is designed to plug into your existing web stack, as long as it supports
the http.Handler
interface for its handlers. (Which is should)
Where's my router?
Like Negroni, Mixer is a BYOR (Bring
your own Router) library. Pick one that is your favorite, and append Mixer
handlers to it using the mixer.Handler
utility function.
What router do you recommend?
I'm really enjoying https://github.com/go-chi/chi these days. Here's a simple example of how Mixer works with Chi:
package main
import (
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/codegangsta/mixer"
)
type Context struct {
mixer.Context
}
func main() {
r := chi.NewRouter()
m := mixer.New(func(c mixer.Context) *Context {
return &Context{c}
})
r.Get("/", m.Handler(func(c *Context) {
fmt.Fprint(c.ResponseWriter(), "Hello world")
}))
http.ListenAndServe(":3000", r)
}
How does this work without reflection?
Mixer uses a very straightforward generics implementation to accomplish its
custom context feature. (Seriously, read the code, it's so freaking simple)
What's the difference between this and context.Context
context.Context
has its uses, but since it was introduced I've seen it be
abused for many things. I believe that type assertions should be used
sparingly, and that context.Context
isn't designed to hold both global and
request scoped values in a very efficient way.
How fast is this?
I don't know. Someone putting together a benchmark would be cool. But I imagine
it's not very slow. Speed will depend on the number of allocations you create
when setting up your context.
I don't want to embed mixer.Context
into my own context?
You don't have to. If you want to keep your context pure of any third party
nonsense, you can do that too. Heck, you can even make your contact an
Interface if that is your sort of thing.
Contributing
I don't know how much more we could add to this that would be valuable, but if you have ideas that's great. Submit a Pull Request with a new feature, bug fix, or link to your project that plays nicely with Mixer.
About
Mixer is distributed under the MIT license, see LICENSE for more details.
Mixer is obsessively designed by none other than the Code Gangsta