Build Status GoDoc GitHub release (latest SemVer)
Box is an HTTP router to speed up development. Box supports URL parameters, interceptors, magic handlers and introspection documentation.
package main import ( "github.com/fulldump/box" ) func main() { b := box.NewBox() b.HandleFunc("GET", "/hello", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("World!")) }) b.ListenAndServe() // listening at http://localhost:8080 }
b := box.NewBox() type MyResponse struct { Name string Age int } b.Handle("GET", "/hello", func(w http.ResponseWriter, r *http.Request) MyResponse { return MyResponse{ Name: "Fulanez", Age: 33, } })
b := box.NewBox() b.Handle("GET", "/articles/{article-id}", func(w http.ResponseWriter, r *http.Request) string { articleID := r.PathValue("article-id") return "ArticleID is " + articleID })
Or before go 1.22:
articleID := box.Param(r, "article-id")
type CreateArticleRequest struct { Title string Text string } type Article struct { Id string `json:"id"` Title string `json:"title"` Text string `json:"text"` Created time.Time `json:"created"` } b := box.NewBox() b.Handle("POST", "/articles", func(input CreateArticleRequest) Article { fmt.Println("Persist new article...", input) return Article{ Id: "my-new-id", Title: input.Title, Text: input.Text, Created: time.Unix(1674762079, 0), } })
Interceptors, also known as middlewares, are pieces of code that are executed in order before the handler to provide common functionality:
- Do things before and/or after the handler execution
- Cut the execution and stop executing the rest of interceptors and handler
- Inject items into the context
func ListArticles() { /* ... */ } func CreateArticles() { /* ... */ } func GetArticle() { /* ... */ } func DeleteArticle() { /* ... */ } func main() { b := box.NewBox() b.Use(box.AccessLog) // use middlewares to print logs b.Use(box.PrettyError) // use middlewares return pretty errors b.Handle("GET", "/articles", ListArticles) b.Handle("POST", "/articles", CreateArticles) b.Handle("GET", "/articles/{article-id}", GetArticle) b.Handle("DELETE", "/articles/{article-id}", DeleteArticle) }
b := box.NewBox() b.Use(box.PrettyError) b.Handle("GET", "/articles", func() (*Article, error) { return nil, errors.New("could not connect to the database") }) go b.ListenAndServe() resp, _ := http.Get(s.URL + "/articles") io.Copy(os.Stdout, resp.Body) // could not connect to the database
Groups are a neat way to organize and compose big APIs and also to limit the scope of interceptors.
b := box.NewBox() v0 := b.Group("/v0") v0.Use(box.SetResponseHeader("Content-Type", "application/json")) v0.Handle("GET", "/articles", ListArticles) v0.Handle("POST", "/articles", CreateArticle)
Interceptors are very useful to reuse logic in a very convenient and modular way.
Here is a sample interceptor that does nothing:
func MyCustomInterceptor(next box.H) box.H { return func(ctx context.Context) { // do something before the handler next(ctx) // continue the flow // do something after the handler } }
The following interceptor returns a Server header:
func MyCustomInterceptor(next box.H) box.H { return func(ctx context.Context) { w := box.GetResponse(ctx) w.Header().Set("Server", "MyServer") next(ctx) // continue the flow } } func main() { b := box.NewBox() b.Use(MyCustomInterceptor) }
Sometimes interceptors can be generalized to cover a wider set of use cases. For example, the following interceptor can set any response header and can be used multiple times.
func SetResponseHeader(key, value string) box.I { return func(next box.H) box.H { return func(ctx context.Context) { box.GetResponse(ctx).Header().Set(key, value) next(ctx) } } } func main() { b := box.NewBox() b.Use( box.SetResponseHeader("Server", "My server name"), box.SetResponseHeader("Version", "v3.2.1"), ) }
Leverage all the API information you have already defined with Box to generate your OpenAPI specification, including your types.
Just use the function boxopenapi.Spec and publish your spec:
func main() { b := box.NewBox() // ... define all your handlers spec := boxopenapi.Spec(b) spec.Info.Title = "My service" spec.Info.Version = "1.0" spec.Servers = []boxopenapi.Server{ { Url: "http://localhost:8080", }, } b.Handle("GET", "/openapi.json", func() any { return spec }) }