A minimalistic web framework with route grouping and middleware chaining
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.travis.yml
LICENSE
README.md
context.go
context_test.go
group.go
handler.go
middleware.go
middleware_test.go
min.go
min_suite_test.go
route_grouping_test.go
router.go
router_test.go

README.md

min

v0.1.1

GoDoc Go Report Card Build Status Codecov GitHub

min is a BYO*, minimalistic web framework that harnesses the power of context and httprouter, and adds some functionality on top—namely, middleware chaining and route grouping. It's meant to be used on projects large and small that require flexibility, and varying degrees of custom code and architecture. min provides the routing, you provide the app.

This package takes some inspiration from design decisions in chi and gin.

Usage

min is designed to be as elegant and as close to "the right way to do things" as possible. Which means that it doesn't implement a lot of custom types, or does a lot of magic. It relies heavily on context and regular types from net/http.

Hello World

You can initialize a new instance of the Min type with an httprouter.Router you provide, or you can pass nil to min.New and a new, default router will be created.

import (
    "fmt"
    "net/http"

    "github.com/arturovm/min"
)

func main() {
    m := min.New(nil)

    m.Get("/", helloWorld)

    http.ListenAndServe(":8080", m)
}

func helloWorld(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello world!")
}

Route Parameters

min supports all the syntax variations for defining route parameters that httprouter does. You then simply use the helper function min.GetParam to access them.

import (
    "fmt"
    "net/http"

    "github.com/arturovm/min"
)

func main() {
    m := min.New(nil)

    m.Get("/:name", greet)

    http.ListenAndServe(":8080", m)
}

func greet(w http.ResponseWriter, r *http.Request) {
    name := min.GetParam(r, "name")
    fmt.Fprintf(w, "hello %s!", name)
}

Route Grouping

import (
    "fmt"
    "net/http"

    "github.com/arturovm/min"
)

func main() {
    m := min.New(nil)

    apiRouter := m.Group("/api")
    {
        // GET /api
        apiRouter.Get("/", apiRoot)
        // GET /api/ignacio
        apiRouter.Get("/:name", greet)
    }

    http.ListenAndServe(":8080", m)
}

func apiRoot(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "api root")
}

func greet(w http.ResponseWriter, r *http.Request) {
    name := min.GetParam(r, "name")
    fmt.Fprintf(w, "hello %s!", name)
}

Middleware

Middleware in min are simply functions that take an http.Handler (the one next in the chain) and return another one. They are resolved in the order that they are declared.

min users are meant to take advantage of context to make better use of middleware.

import (
    "context"
    "fmt"
    "log"
    "net/http"

    "github.com/arturovm/min"
)

func main() {
    m := min.New(nil)
    m.Use(logger)
    m.Use(printer)

    apiRouter := m.Group("/api")
    {
        apiRouter.Get("/", apiRoot)
        nameRouter := apiRouter.Group("/:name")
        {
            // Every request sent to routes defined on this sub-router will now
            // have a reference to a name in its context.
            // Useful for RESTful design.
            nameRouter.Use(nameExtractor)

            // GET /api/ignacio
            nameRouter.Get("/", greet)
            // GET /api/ignacio/goodbye
            nameRouter.Get("/goodbye", goodbye)
        }
    }

    http.ListenAndServe(":8080", m)
}

// -- Middleware --

// a simple logger
func logger(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("| %s %s", r.Method, r.URL)
        next.ServeHTTP(w, r)
    })
}

// a useless middleware that prints text
func printer(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("this prints some text")
        next.ServeHTTP(w, r)
    })
}

// extracts a name from the URL and injects it into the request's context
func nameExtractor(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        name := min.GetParam(r, "name")
        ctx := context.WithValue(r.Context(), "name", name)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// -- Handlers --

func apiRoot(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "api root")
}

// greets the user with :name
func greet(w http.ResponseWriter, r *http.Request) {
    name := r.Context().Value("name").(string)
    fmt.Fprintf(w, "hello %s!", name)
}

// says "bye" to the user with :name
func goodbye(w http.ResponseWriter, r *http.Request) {
    name := r.Context().Value("name").(string)
    fmt.Fprintf(w, "bye %s!", name)
}

Base Router

If you need access to the underlying httprouter.Router, you can use the GetBaseRouter method.

import (
    "github.com/arturovm/min"
)

func main() {
    m := min.New(nil)

    _ = m.GetBaseRouter()
}