lightweight, idiomatic and composable router for building Go HTTP services

chi

GoDoc Widget Travis Widget

chi is a lightweight, idiomatic and composable router for building Go HTTP services. It's especially good at helping you write large REST API services that are kept maintainable as your project grows and changes. chi is built on the new context package introduced in Go 1.7 to handle signaling, cancelation and request-scoped values across a handler chain.

The focus of the project has been to seek out an elegant and comfortable design for writing REST API servers, written during the development of the Pressly API service that powers our public API service, which in turn powers all of our client-side applications.

The key considerations of chi's design are: project structure, maintainability, standard http handlers (stdlib-only), developer productivity, and deconstructing a large system into many small parts. The core router github.com/go-chi/chi is quite small (less than 1000 LOC), but we've also included some useful/optional subpackages: middleware, render and docgen. We hope you enjoy it too!

Install

go get -u github.com/go-chi/chi/v5

Features

  • Lightweight - cloc'd in ~1000 LOC for the chi router
  • Fast - yes, see benchmarks
  • 100% compatible with net/http - use any http or middleware pkg in the ecosystem that is also compatible with net/http
  • Designed for modular/composable APIs - middlewares, inline middlewares, route groups and sub-router mounting
  • Context control - built on new context package, providing value chaining, cancellations and timeouts
  • Robust - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see discussion)
  • Doc generation - docgen auto-generates routing documentation from your source to JSON or Markdown
  • Go.mod support - v1.x of chi (starting from v1.5.0), now has go.mod support (see CHANGELOG)
  • No external dependencies - plain ol' Go stdlib + net/http

Examples

See _examples/ for a variety of examples.

As easy as:

package main

import (
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

func main() {
	r := chi.NewRouter()
	r.Use(middleware.Logger)
	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("welcome"))
	})
	http.ListenAndServe(":3000", r)
}

REST Preview:

Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs in JSON (routes.json) and in Markdown (routes.md).

I highly recommend reading the source of the examples listed above, they will show you all the features of chi and serve as a good form of documentation.

import (
  //...
  "context"
  "github.com/go-chi/chi/v5"
  "github.com/go-chi/chi/v5/middleware"
)

func main() {
  r := chi.NewRouter()

  // A good base middleware stack
  r.Use(middleware.RequestID)
  r.Use(middleware.RealIP)
  r.Use(middleware.Logger)
  r.Use(middleware.Recoverer)

  // Set a timeout value on the request context (ctx), that will signal
  // through ctx.Done() that the request has timed out and further
  // processing should be stopped.
  r.Use(middleware.Timeout(60 * time.Second))

  r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hi"))
  })

  // RESTy routes for "articles" resource
  r.Route("/articles", func(r chi.Router) {
    r.With(paginate).Get("/", listArticles)                           // GET /articles
    r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017

    r.Post("/", createArticle)                                        // POST /articles
    r.Get("/search", searchArticles)                                  // GET /articles/search

    // Regexp url parameters:
    r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug)                // GET /articles/home-is-toronto

    // Subrouters:
    r.Route("/{articleID}", func(r chi.Router) {
      r.Use(ArticleCtx)
      r.Get("/", getArticle)                                          // GET /articles/123
      r.Put("/", updateArticle)                                       // PUT /articles/123
      r.Delete("/", deleteArticle)                                    // DELETE /articles/123
    })
  })

  // Mount the admin sub-router
  r.Mount("/admin", adminRouter())

  http.ListenAndServe(":3333", r)
}

func ArticleCtx(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    articleID := chi.URLParam(r, "articleID")
    article, err := dbGetArticle(articleID)
    if err != nil {
      http.Error(w, http.StatusText(404), 404)
      return
    }
    ctx := context.WithValue(r.Context(), "article", article)
    next.ServeHTTP(w, r.WithContext(ctx))
  })
}

func getArticle(w http.ResponseWriter, r *http.Request) {
  ctx := r.Context()
  article, ok := ctx.Value("article").(*Article)
  if !ok {
    http.Error(w, http.StatusText(422), 422)
    return
  }
  w.Write([]byte(fmt.Sprintf("title:%s", article.Title)))
}

// A completely separate router for administrator routes
func adminRouter() http.Handler {
  r := chi.NewRouter()
  r.Use(AdminOnly)
  r.Get("/", adminIndex)
  r.Get("/accounts", adminListAccounts)
  return r
}

func AdminOnly(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    perm, ok := ctx.Value("acl.permission").(YourPermissionType)
    if !ok || !perm.IsAdmin() {
      http.Error(w, http.StatusText(403), 403)
      return
    }
    next.ServeHTTP(w, r)
  })
}

Router interface

chi's router is based on a kind of Patricia Radix trie. The router is fully compatible with net/http.

Built on top of the tree is the Router interface:

// Router consisting of the core routing methods used by chi's Mux,
// using only the standard net/http.
type Router interface {
	http.Handler
	Routes

	// Use appends one or more middlewares onto the Router stack.
	Use(middlewares ...func(http.Handler) http.Handler)

	// With adds inline middlewares for an endpoint handler.
	With(middlewares ...func(http.Handler) http.Handler) Router

	// Group adds a new inline-Router along the current routing
	// path, with a fresh middleware stack for the inline-Router.
	Group(fn func(r Router)) Router

	// Route mounts a sub-Router along a `pattern`` string.
	Route(pattern string, fn func(r Router)) Router

	// Mount attaches another http.Handler along ./pattern/*
	Mount(pattern string, h http.Handler)

	// Handle and HandleFunc adds routes for `pattern` that matches
	// all HTTP methods.
	Handle(pattern string, h http.Handler)
	HandleFunc(pattern string, h http.HandlerFunc)

	// Method and MethodFunc adds routes for `pattern` that matches
	// the `method` HTTP method.
	Method(method, pattern string, h http.Handler)
	MethodFunc(method, pattern string, h http.HandlerFunc)

	// HTTP-method routing along `pattern`
	Connect(pattern string, h http.HandlerFunc)
	Delete(pattern string, h http.HandlerFunc)
	Get(pattern string, h http.HandlerFunc)
	Head(pattern string, h http.HandlerFunc)
	Options(pattern string, h http.HandlerFunc)
	Patch(pattern string, h http.HandlerFunc)
	Post(pattern string, h http.HandlerFunc)
	Put(pattern string, h http.HandlerFunc)
	Trace(pattern string, h http.HandlerFunc)

	// NotFound defines a handler to respond whenever a route could
	// not be found.
	NotFound(h http.HandlerFunc)

	// MethodNotAllowed defines a handler to respond whenever a method is
	// not allowed.
	MethodNotAllowed(h http.HandlerFunc)
}

// Routes interface adds two methods for router traversal, which is also
// used by the github.com/go-chi/docgen package to generate documentation for Routers.
type Routes interface {
	// Routes returns the routing tree in an easily traversable structure.
	Routes() []Route

	// Middlewares returns the list of middlewares in use by the router.
	Middlewares() Middlewares

	// Match searches the routing tree for a handler that matches
	// the method/path - similar to routing a http request, but without
	// executing the handler thereafter.
	Match(rctx *Context, method, path string) bool
}

Each routing method accepts a URL pattern and chain of handlers. The URL pattern supports named params (ie. /users/{userID}) and wildcards (ie. /admin/*). URL parameters can be fetched at runtime by calling chi.URLParam(r, "userID") for named parameters and chi.URLParam(r, "*") for a wildcard parameter.

Middleware handlers

chi's middlewares are just stdlib net/http middleware handlers. There is nothing special about them, which means the router and all the tooling is designed to be compatible and friendly with any middleware in the community. This offers much better extensibility and reuse of packages and is at the heart of chi's purpose.

Here is an example of a standard net/http middleware where we assign a context key "user" the value of "123". This middleware sets a hypothetical user identifier on the request context and calls the next handler in the chain.

// HTTP middleware setting a value on the request context
func MyMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // create new context from `r` request context, and assign key `"user"`
    // to value of `"123"`
    ctx := context.WithValue(r.Context(), "user", "123")

    // call the next handler in the chain, passing the response writer and
    // the updated request object with the new context value.
    //
    // note: context.Context values are nested, so any previously set
    // values will be accessible as well, and the new `"user"` key
    // will be accessible from this point forward.
    next.ServeHTTP(w, r.WithContext(ctx))
  })
}

Request handlers

chi uses standard net/http request handlers. This little snippet is an example of a http.Handler func that reads a user identifier from the request context - hypothetically, identifying the user sending an authenticated request, validated+set by a previous middleware handler.

// HTTP handler accessing data from the request context.
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
  // here we read from the request context and fetch out `"user"` key set in
  // the MyMiddleware example above.
  user := r.Context().Value("user").(string)

  // respond to the client
  w.Write([]byte(fmt.Sprintf("hi %s", user)))
}

URL parameters

chi's router parses and stores URL parameters right onto the request context. Here is an example of how to access URL params in your net/http handlers. And of course, middlewares are able to access the same information.

// HTTP handler accessing the url routing parameters.
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
  // fetch the url parameter `"userID"` from the request of a matching
  // routing pattern. An example routing pattern could be: /users/{userID}
  userID := chi.URLParam(r, "userID")

  // fetch `"key"` from the request context
  ctx := r.Context()
  key := ctx.Value("key").(string)

  // respond to the client
  w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key)))
}

Middlewares

chi comes equipped with an optional middleware package, providing a suite of standard net/http middlewares. Please note, any middleware in the ecosystem that is also compatible with net/http can be used with chi's mux.

Core middlewares


chi/middleware Handler description
AllowContentEncoding Enforces a whitelist of request Content-Encoding headers
AllowContentType Explicit whitelist of accepted request Content-Types
BasicAuth Basic HTTP authentication
Compress Gzip compression for clients that accept compressed responses
ContentCharset Ensure charset for Content-Type request headers
CleanPath Clean double slashes from request path
GetHead Automatically route undefined HEAD requests to GET handlers
Heartbeat Monitoring endpoint to check the servers pulse
Logger Logs the start and end of each request with the elapsed processing time
NoCache Sets response headers to prevent clients from caching
Profiler Easily attach net/http/pprof to your routers
RealIP Sets a http.Request's RemoteAddr to either X-Forwarded-For or X-Real-IP
Recoverer Gracefully absorb panics and prints the stack trace
RequestID Injects a request ID into the context of each request
RedirectSlashes Redirect slashes on routing paths
RouteHeaders Route handling for request headers
SetHeader Short-hand middleware to set a response header key/value
StripSlashes Strip slashes on routing paths
Throttle Puts a ceiling on the number of concurrent requests
Timeout Signals to the request context when the timeout deadline is reached
URLFormat Parse extension from url and put it on request context
WithValue Short-hand middleware to set a key/value on the request context

Extra middlewares & packages

Please see https://github.com/go-chi for additional packages.


package description
cors Cross-origin resource sharing (CORS)
docgen Print chi.Router routes at runtime
jwtauth JWT authentication
hostrouter Domain/host based request routing
httplog Small but powerful structured HTTP request logging
httprate HTTP request rate limiter
httptracer HTTP request performance tracing library
httpvcr Write deterministic tests for external sources
stampede HTTP request coalescer

context?

context is a tiny pkg that provides simple interface to signal context across call stacks and goroutines. It was originally written by Sameer Ajmani and is available in stdlib since go1.7.

Learn more at https://blog.golang.org/context

and..

Benchmarks

The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark

Results as of Nov 29, 2020 with Go 1.15.5 on Linux AMD 3950x

BenchmarkChi_Param          	3075895	        384 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_Param5         	2116603	        566 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_Param20        	 964117	       1227 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_ParamWrite     	2863413	        420 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GithubStatic   	3045488	        395 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GithubParam    	2204115	        540 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GithubAll      	  10000	     113811 ns/op	    81203 B/op    406 allocs/op
BenchmarkChi_GPlusStatic    	3337485	        359 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GPlusParam     	2825853	        423 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GPlus2Params   	2471697	        483 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_GPlusAll       	 194220	       5950 ns/op	     5200 B/op     26 allocs/op
BenchmarkChi_ParseStatic    	3365324	        356 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_ParseParam     	2976614	        404 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_Parse2Params   	2638084	        439 ns/op	      400 B/op      2 allocs/op
BenchmarkChi_ParseAll       	 109567	      11295 ns/op	    10400 B/op     52 allocs/op
BenchmarkChi_StaticAll      	  16846	      71308 ns/op	    62802 B/op    314 allocs/op

Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc

NOTE: the allocs in the benchmark above are from the calls to http.Request's WithContext(context.Context) method that clones the http.Request, sets the Context() on the duplicated (alloc'd) request and returns it the new request object. This is just how setting context on a request in Go works.

Go module support & note on chi's versioning

  • Go.mod support means we reset our versioning starting from v1.5 (see CHANGELOG)
  • All older tags are preserved, are backwards-compatible and will "just work" as they
  • Brand new systems can run go get -u github.com/go-chi/chi as normal, or go get -u github.com/go-chi/[email protected] to install chi, which will install v1.x+ built with go.mod support, starting from v1.5.0.
  • For existing projects who want to upgrade to the latest go.mod version, run: go get -u github.com/go-chi/[email protected], which will get you on the go.mod version line (as Go's mod cache may still remember v4.x).
  • Any breaking changes will bump a "minor" release and backwards-compatible improvements/fixes will bump a "tiny" release.

Credits

We'll be more than happy to see your contributions!

Beyond REST

chi is just a http router that lets you decompose request handling into many smaller layers. Many companies use chi to write REST services for their public APIs. But, REST is just a convention for managing state via HTTP, and there's a lot of other pieces required to write a complete client-server system or network of microservices.

Looking beyond REST, I also recommend some newer works in the field:

  • webrpc - Web-focused RPC client+server framework with code-gen
  • gRPC - Google's RPC framework via protobufs
  • graphql - Declarative query language
  • NATS - lightweight pub-sub

License

Copyright (c) 2015-present Peter Kieltyka

Licensed under MIT License

Comments
  • chi@v1.5.x mod issues

    [email protected] mod issues

    I have tried to switch to v1.5.0 from v4.1.2+incompatible but because the version number decreased it caused a downgrade of all other dependencies using chi. For example, I have started with this:

    git.example.com/commons/pkg/rest v1.3.2
    github.com/go-chi/chi v4.1.2+incompatible
    github.com/go-chi/render v1.0.1
    

    After running go get github.com/go-chi/[email protected] I got:

    git.example.com/commons/pkg/rest v1.1.0
    github.com/go-chi/chi v1.5.1
    github.com/go-chi/render v1.0.1
    

    As you can see my pkg/rest downgraded to some old version and such side effects can be very unexpected and unpleasant.

    Is there a reason why you decided to go with v1.5.x and not with a more traditional (and less dangerous) approach with v5 (also changing the package name to "/v5")? I'm not sure how to deal with this. In order to avoid those unexpected downgrades I have to have all of my dependencies migrated to v1.5, but unfortunately some of them not under my control.

  • HEAD for GET routes

    HEAD for GET routes

    Standard go http router allows to do HEAD requests to GET endpoints and return the same response but without body. Chi response 405 for GET routes with HEAD method.

  • Add go.mod file

    Add go.mod file

    Perhaps you've been following the discussion around the future of go dependency management? https://research.swtch.com/vgo

    Anyways, a few unrelated people, including me have tried to play with this experiment in our own projects and we are running into problems import chi due to it not having a go.mod file properly advertising the major version as v3.

    Any chance chi could partake in this experiment?

    I'm willing to give creating a proper chi go.mod file a go in a PR if so.

  • Render: managing request and response payload as types

    Render: managing request and response payload as types

    The render subpkg is pretty cool, and its great for managing the different kinds of content types a response may need, including streams. However, one of the important parts for a maintainable API is to manage the request and response payloads (the inputs and the outputs of an endpoint).

    For request payloads, one simple idea is to have a Bind() middleware used in chi's r.With() inline middleware routing, that will take a request body and unmarshal it to a kind of struct. Perhaps.

    type ArticleRequest {
      ID int64 `json:"id"`
      Name string `json:"name"`
    }
    // ...
    r.Get("/", index)
    r.With(render.Request(&ArticleRequest{})).Post("/articles", newArticle)
    

    .. something like that.. the render.Request() would make a new &ArticleRequest{} object and put it on the context under render.RequestKey or something.. its a decent plan, except it would require some kind of reflection to make the new &ArticleRequest{} object.

    As for response payloads, that should be much simpler, just..

    type ArticleResponse {
      *data.Article // embed the core Article type
      // add more methods on it..
    }
    //...
    render.Respond(w, r, &ArticleResponse{a})
    

    if someone has other ideas too that would be awesome!

  • Question: HTTP Status Code

    Question: HTTP Status Code

    How do you set the HTTP Status code to something other than 200? I have a simple test case below and I'm unable to change the HTTP Status code. It is always a 200 OK. I can render my own text(JSON) but this still does NOT set the HTTP Status code to 401!

    package main
    func routes() *chi.Mux {
    	router := chi.NewRouter()
    	router.Use(
    		render.SetContentType(render.ContentTypeJSON),
    		middleware.Logger,
    		middleware.DefaultCompress,
    		middleware.RedirectSlashes,
    		middleware.Recoverer,
    		middleware.RealIP,
    	)
    	router.Get("/test", test)
    	return router
    }
    
    func test(w http.ResponseWriter, r *http.Request) {
    	w.WriteHeader(401)
    	http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
    	render.Status(r, 401)
    	render.Render(w, r, ErrInvalidRequest())
    	return
    }
    type ErrResponse struct {
    	Err            error `json:"-"` // low-level runtime error
    	HTTPStatusCode int   `json:"-"` // http response status code
    
    	StatusText string `json:"status"`          // user-level status message
    	AppCode    int64  `json:"code,omitempty"`  // application-specific error code
    	ErrorText  string `json:"error,omitempty"` // application-level error message, for debugging
    }
    
    func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error {
    	render.Status(r, e.HTTPStatusCode)
    	return nil
    }
    
    func ErrInvalidRequest() render.Renderer {
    	return &ErrResponse{
    		// Err:            err,
    		HTTPStatusCode: 401,
    		StatusText:     "Invalid request.",
    		ErrorText:      "Invalid request.",
    	}
    }
    func main() {
    	router := routes()
    
    	walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
    		log.Printf("%s %s\n", method, route)
    		return nil
    	}
    	if err := chi.Walk(router, walkFunc); err != nil {
    		log.Panicf("Loggin err: %s\n", err.Error())
    	}
    	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", cfg.SrvPort), router))
    }
    

    Results - The text is changed but NOT the HTTP status code

    curl --request GET  --url http://localhost:8080/test -i
    HTTP/1.1 200 OK
    Content-Type: text/plain; charset=utf-8
    X-Content-Type-Options: nosniff
    Date: Tue, 19 Feb 2019 16:56:46 GMT
    Content-Length: 57
    
    Unauthorized
    {"status":401,"message":"Invalid request."}
    
  • Route params mixup

    Route params mixup

    First of all, love the project & the philosophy behind it - it's the perfect approach for Go 1.7+ IMO :thumbsup:

    But I'm having an issue with some supporting some legacy route formats. Requests map to the correct handlers but the parameters don't match up correctly. Here's a simplified example replacing the routes in TestTree to show the issue I'm having:

    func TestTree(t *testing.T) {
        hDate := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
        hCat := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
    
        tr := &node{}
    
        tr.InsertRoute(mGET, "/items/:year/:month", hDate)
        tr.InsertRoute(mGET, "/items/:category", hCat)
    
        tests := []struct {
            r string            // input request path
            h http.Handler      // output matched handler
            p map[string]string // output params
        }{
            {r: "/items/2016/08", h: hDate, p: map[string]string{"year": "2016", "month": "08"}},
            {r: "/items/things", h: hCat, p: map[string]string{"category": "things"}},
        }
    

    That results in:

    --- FAIL: TestTree (0.00s)
        tree_test.go:59: input [1]: find '/items/things' expecting params:map[category:things] , got:map[year:things]
    

    Note that the /items/:category handler is correctly used but the parameter is called :year instead from the previous route.

    It looks like it's due to how the tree is built with the name of the parameter ignored so it then gets the name from the previous node:

    2016/08/23 10:40:56 [node 0 parent:0] typ:0 prefix: label: numEdges:1 isLeaf:false
    2016/08/23 10:40:56 [node 1 parent:0] typ:0 prefix:/items/ label:/ numEdges:1 isLeaf:false
    2016/08/23 10:40:56 [node 2 parent:1] typ:2 prefix::year label:: numEdges:1 isLeaf:true handler:map[512:<nil> 4:0x90450]
    2016/08/23 10:40:56 [node 3 parent:2] typ:0 prefix:/ label:/ numEdges:1 isLeaf:false
    2016/08/23 10:40:56 [node 4 parent:3] typ:2 prefix::month label:: numEdges:0 isLeaf:true handler:map[512:<nil> 4:0x90440]
    

    Is this simply not supported (I know many Go routers are strict about the route variations, others such as Echo cope better with suffix variations) or is it a bug?

    Many thanks.

  • chi v3 roadmap

    chi v3 roadmap

    • [x] Fix bugs #78 #100 - relating to param keys overlapping between different endpoints
    • [x] Change param syntax from /articles/:id to /articles/{id} which improves expressiveness and delimiting (#167)
    • [x] Add support for regexp via /articles/{name:[a-z]}
    • [x] Add support for http.Handler for method routing, as in #176
    • [ ] Add a few param helpers with as chi.URLParamInt64(r, "bookID")
    • [x] Solve: Subrouters stacked, both using /{id} param key - panic? - #61
  • Feature Request: Add URL param/query helpers

    Feature Request: Add URL param/query helpers

    In Chi, there is a helper to get the URL param. It has this signature:

    func Param(r *http.Request, key string) string
    

    I found I would often have to import strconv and convert a number param to an int. So I forked and created a method with a signature like so:

    func ParamInt(r *http.Request, key string) (int, error)
    

    Then I came across an issue with query parameters. I want to get a query parameter by name without having to parse it manually on each endpoint, and they also might be numbers that should be converted to ints.

    So I created these two signatures as well:

    func QueryString(r *http.Request, key string) string
    func QueryStringInt(r *http.Request, key string) (int, error)
    

    Here is the logic for everything:

    context.go:

    // Param returns the url parameter from a http.Request object.
    func Param(r *http.Request, key string) string {
    	if rctx := RouteContext(r.Context()); rctx != nil {
    		return rctx.Param(key)
    	}
    	return ""
    }
    
    // ParamInt will get a param from the request URL and attempt to parse it as an int.
    func ParamInt(r *http.Request, key string) (int, error) {
    	val, err := strconv.Atoi(Param(r, key))
    	if err != nil {
    		return 0, err
    	}
    	return val, nil
    }
    
    // Query will get a query parameter by key.
    func QueryString(r *http.Request, key string) string {
    	if rctx := RouteContext(r.Context()); rctx != nil {
    		return rctx.QueryString(key)
    	}
    	return ""
    }
    
    // QueryInt will get a query parameter by key and convert it to an int or return an error.
    func QueryStringInt(r *http.Request, key string) (int, error) {
    	val, err := strconv.Atoi(QueryString(r, key))
    	if err != nil {
    		return 0, err
    	}
    	return val, nil
    }
    

    Would you be willing to implement this so I don't need to use a forked version? :P

  • http: multiple response.WriteHeader calls due CloseNotify

    http: multiple response.WriteHeader calls due CloseNotify

    Adding CloseNotify middleware causing sometimes "multiple response.WriteHeader calls" warning in normal cases, i.e. request was not canceled by caller, but finished normally.

    Probably sometimes https://github.com/pressly/chi/blob/master/middleware/closenotify.go#L32 reached after normal completion? Not sure what the sequence, but I suspect client completed (and disconnected), connection closed and in select it gets closeNotifyCh

  • Wildcard

    Wildcard "*" behaves unexpectedly when used in conjunction with a nested router which has url parameters.

    Given:

    	r := chi.NewRouter()
    
    	r.Get("/bare/{param}", testStar)
    	r.Get("/bare/{param}/*", testStar)
    
    	r.Route("/case0", func(r chi.Router) {
    		r.Get("/{param}", testStar) // (1)
    		r.Get("/{param}/*", testStar) // (2)
    	})
    

    In case (1) wildcard "*" evaluates to browse/<path_segment> given url <path>/browse/<path_segment> In case (2) wildcard "*" evaluates to <sub_path_1>/<sub_path_2> given url <path>/browse/<path_segment>/<sub_path_1>/<sub_path_2>

    UPDATE: (Minimum reproducible example)

    package main
    
    import (
    	"fmt"
    	"io/ioutil"
    	"net/http"
    
    	"github.com/go-chi/chi"
    )
    
    func main() {
    	r := chi.NewRouter()
    
    	r.Get("/bare/{param}", testStar)
    	r.Get("/bare/{param}/*", testStar)
    
    	{ // case 0: Mounting a router using callback.
    		r.Route("/case0", func(r chi.Router) {
    			r.Get("/{param}", testStar)
    			r.Get("/{param}/*", testStar)
    		})
    	}
    	{ // case 1: Explicitly mounting a router.
    		sr := chi.NewRouter()
    		sr.Get("/{param}", testStar)
    		sr.Get("/{param}/*", testStar)
    		r.Mount("/case1", sr)
    	}
    
    	go http.ListenAndServe(":8080", r)
    
    	testCases := []struct {
    		url    string
    		params map[string]string
    	}{
    		{url: "value", params: map[string]string{
    			"param": "value",
    			"*":     "",
    		}},
    		{url: "value/sub/path", params: map[string]string{
    			"param": "value",
    			"*":     "sub/path",
    		}},
    	}
    
    	prefixes := []string{
    		"bare",
    		"case0",
    		"case1",
    	}
    
    	for _, prefix := range prefixes {
    		for _, tc := range testCases {
    			url := fmt.Sprintf("http://localhost:8080/%v/%v", prefix, tc.url)
    			printHttp(url, tc.params)
    		}
    	}
    }
    
    func testStar(rw http.ResponseWriter, r *http.Request) {
    	ctx := chi.RouteContext(r.Context())
    	fmt.Fprintf(rw, "pattern: %v\n", ctx.RoutePattern())
    	fmt.Fprintf(rw, "  param: (%v) *: (%v)", ctx.URLParam("param"), ctx.URLParam("*"))
    }
    
    func printHttp(url string, params map[string]string) {
    	r, err := http.Get(url)
    	if err != nil {
    		fmt.Println("url:", url, "\n\terr:", err)
    	}
    	data, _ := ioutil.ReadAll(r.Body)
    
    	fmt.Println("url:", url)
    
    	fmt.Println("--   ACTUAL:")
    	fmt.Println(string(data))
    
    	fmt.Println("-- EXPECTED:")
    	fmt.Printf("  param: (%v) *: (%v)", params["param"], params["*"])
    
    	fmt.Printf("\n\n")
    }
    

    Output:

    url: http://localhost:8080/bare/value
    --   ACTUAL:
    pattern: /bare/{param}
      param: (value) *: ()
    -- EXPECTED:
      param: (value) *: ()
    
    url: http://localhost:8080/bare/value/sub/path
    --   ACTUAL:
    pattern: /bare/{param}/*
      param: (value) *: (sub/path)
    -- EXPECTED:
      param: (value) *: (sub/path)
    
    url: http://localhost:8080/case0/value
    --   ACTUAL:
    pattern: /case0/{param}
      param: (value) *: (value)
    -- EXPECTED:
      param: (value) *: ()
    
    url: http://localhost:8080/case0/value/sub/path
    --   ACTUAL:
    pattern: /case0/{param}/*
      param: (value) *: (sub/path)
    -- EXPECTED:
      param: (value) *: (sub/path)
    
    url: http://localhost:8080/case1/value
    --   ACTUAL:
    pattern: /case1/{param}
      param: (value) *: (value)
    -- EXPECTED:
      param: (value) *: ()
    
    url: http://localhost:8080/case1/value/sub/path
    --   ACTUAL:
    pattern: /case1/{param}/*
      param: (value) *: (sub/path)
    -- EXPECTED:
      param: (value) *: (sub/path)
    
  • chi v4

    chi v4

    chi is a pretty minimal library and has been hardened over the years, so v4 won't introduce any big changes other then a few minor things like..

    • [x] chi's Go support will be inline with Go's own support - #366 - therefore, we remove support for Go 1.7 and 1.8, and stop tracking 1.9
    • [x] return 404 for mux with empty routes #362
    • [x] additional check to ensure wildcard is at the end of a url pattern #333
    • [x] middleware: deprecate support for CloseNotifier - #347
    • [ ] Optimal Go module handling #302 - some community submissions: #364 #370 #379

    :)

  • Exclude certain routes or patterns from logger middleware

    Exclude certain routes or patterns from logger middleware

    It's pretty common to want some routes not included in the logging, an obvious example is a health check when being used by a load balancer or other probe, or a Prometheus metrics endpoint - generates thousands of lines unnecessary output & logging. In addition there might be certain patterns like email address which accidentally get logged and leak PII

    I wanted to use the logging middleware but also exclude some routes, however I can find no way to do this without providing my own full implementation of the LogFormatter interface.

    Ideally an array of RegEx patterns could be configured for logging exclusion, which are applied to the URI of the requests

  • middleware: SetAllowHeader adds the Allow header on 405 response

    middleware: SetAllowHeader adds the Allow header on 405 response

    By adding the Allow header with the lists of available methods on 405 Method Not Found error, this PR makes chi compliant with rfc9110, because this behaviour is not currently supported in Chi.

    Note that it also make chi look better on this article (that will need to be updated). This article is the first to appear when searching on google "golang choose router".

    It adds a small but non-negligable performance offset, that's why it is proposed as a middleware

    Closes #446.

    Don't hesitate to ask for changes. Thanks for this amazing package :)


    Performance details

    BenchmarkSetAllowHeaderWhen405-8         1509776               776.7 ns/op           483 B/op          2 allocs/op
    BenchmarkSetAllowHeaderWhen200-8         5560737               209.7 ns/op           336 B/op          3 allocs/op
    BenchmarkWithoutSetAllowHeader-8         6710652               163.7 ns/op           332 B/op          3 allocs/op
    
    • BenchmarkWithoutSetAllowHeader is without the middleware, to compare
    • BenchmarkSetAllowHeaderWhen200 is when the middleware is activated but not really used - it's a 200 response code
    • BenchmarkSetAllowHeaderWhen405 is when the middleware is activated and used
  • Expose FindRoute on Mux to enable access to it in custom middleware

    Expose FindRoute on Mux to enable access to it in custom middleware

    We are building a custom middleware which needs to know the route that matched the current request path. Unfortunately, the only available option Match just returns a yay or nay. Since this method has access to the actual route that matches, can we add a FindRoute on Mux that will delegate the actual work to tree.FindRoute ?

    Is there an alternate way to determine the route that matches the current http request path in a custom middleware?

  • Regex param doesn't match dots

    Regex param doesn't match dots

    Example program:

    package main
    
    import (
    	"fmt"
    	"net/http"
    	"net/http/httptest"
    
    	"github.com/go-chi/chi/v5"
    )
    
    func main() {
    	mux := chi.NewMux()
    	mux.Get("/{param:.+}.json", func(w http.ResponseWriter, r *http.Request) {
    		param := chi.URLParam(r, "param")
    		_, _ = fmt.Fprintf(w, "param=%s", param)
    	})
    
    	rec := httptest.NewRecorder()
    	req := httptest.NewRequest(http.MethodGet, "/param.json", nil)
    	mux.ServeHTTP(rec, req)
    
    	fmt.Printf("code: %d, body: %s\n", rec.Code, rec.Body)
    
    	rec = httptest.NewRecorder()
    	req = httptest.NewRequest(http.MethodGet, "/param.with.dots.json", nil)
    	mux.ServeHTTP(rec, req)
    
    	fmt.Printf("code: %d, body: %s\n", rec.Code, rec.Body)
    }
    

    Expected:

    code: 200, body: param=param
    code: 404, body: param=param.with.dots
    

    Got:

    code: 200, body: param=param
    code: 404, body: 404 page not found
    
  • chi.Walk missing middlewares

    chi.Walk missing middlewares

    Hi,

    I was trying to use chi.Walk to get a report of all routes and the middlewares used by every route. I noticed that some of the middlewares were missing from some of the nested routes when using groups.

    Example routes:

    	r := chi.NewRouter()
    
    	r.Use(middleware.RequestID)
    
    	r.Get("/A", func(w http.ResponseWriter, r *http.Request) {})
    
    	r.Group(func(r chi.Router) {
    		r.Use(middleware.Timeout(2500 * time.Millisecond))
    
    		r.Get("/B", func(w http.ResponseWriter, r *http.Request) {})
    
    		r.Route("/C", func(r chi.Router) {
    
    			// Walk on the below route does not show the Timeout middleware
    			r.Get("/D", func(w http.ResponseWriter, r *http.Request) {})
    		})
    	})
    

    In the example above, I expected the C/D route to have both RequestId and Timeout middlewares. but chi.Walk only shows RequestID.

    It's possible I'm missing something here and this is behaving as expected.

    Go playground link: https://go.dev/play/p/HKh7cA35Bgx

An extremely fast Go (golang) HTTP router that supports regular expression route matching. Comes with full support for building RESTful APIs.

ozzo-routing You may consider using go-rest-api to jumpstart your new RESTful applications with ozzo-routing. Description ozzo-routing is a Go package

Dec 31, 2022
:rotating_light: Is a lightweight, fast and extensible zero allocation HTTP router for Go used to create customizable frameworks.
:rotating_light: Is a lightweight, fast and extensible zero allocation HTTP router for Go used to create customizable frameworks.

LARS LARS is a fast radix-tree based, zero allocation, HTTP router for Go. view examples. If looking for a more pure Go solution, be sure to check out

Dec 27, 2022
:tongue: CleverGo is a lightweight, feature rich and high performance HTTP router for Go.

CleverGo CleverGo is a lightweight, feature rich and trie based high performance HTTP request router. go get -u clevergo.tech/clevergo English 简体中文 Fe

Nov 17, 2022
Fast, simple, and lightweight HTTP router for Golang

Sariaf Fast, simple and lightweight HTTP router for golang Install go get -u github.com/majidsajadi/sariaf Features Lightweight compatible with net/ht

Aug 19, 2022
Lightweight Router for Golang using net/http standard library with custom route parsing, handler and context.

Go-Lightweight-Router Lightweight Router for Golang using net/http standard library with custom route parsing, handler and context. Further developmen

Nov 3, 2021
Composable framework for writing HTTP handlers in Go.

siesta Siesta is a framework for writing composable HTTP handlers in Go. It supports typed URL parameters, middleware chains, and context passing. Get

Nov 18, 2022
Bxog is a simple and fast HTTP router for Go (HTTP request multiplexer).

Bxog is a simple and fast HTTP router for Go (HTTP request multiplexer). Usage An example of using the multiplexer: package main import ( "io" "net

Dec 26, 2022
xujiajun/gorouter is a simple and fast HTTP router for Go. It is easy to build RESTful APIs and your web framework.

gorouter xujiajun/gorouter is a simple and fast HTTP router for Go. It is easy to build RESTful APIs and your web framework. Motivation I wanted a sim

Dec 8, 2022
Fast and flexible HTTP router
Fast and flexible HTTP router

treemux - fast and flexible HTTP router Basic example Debug logging CORS example Error handling Rate limiting using Redis Gzip compression OpenTelemet

Dec 27, 2022
Go HTTP request router and web framework benchmark

Go HTTP Router Benchmark This benchmark suite aims to compare the performance of HTTP request routers for Go by implementing the routing structure of

Dec 27, 2022
Simple Golang HTTP router
Simple Golang HTTP router

Bellt Simple Golang HTTP router Bellt Package implements a request router with the aim of managing controller actions based on fixed and parameterized

Sep 27, 2022
FastRouter is a fast, flexible HTTP router written in Go.

FastRouter FastRouter is a fast, flexible HTTP router written in Go. FastRouter contains some customizable options, such as TrailingSlashesPolicy, Pan

Sep 27, 2022
Go Server/API micro framework, HTTP request router, multiplexer, mux
Go Server/API micro framework, HTTP request router, multiplexer, mux

?? gorouter Go Server/API micro framework, HTTP request router, multiplexer, mux. ?? ABOUT Contributors: Rafał Lorenz Want to contribute ? Feel free t

Dec 16, 2022
A high performance HTTP request router that scales well

HttpRouter HttpRouter is a lightweight high performance HTTP request router (also called multiplexer or just mux for short) for Go. In contrast to the

Dec 28, 2022
High-speed, flexible tree-based HTTP router for Go.

httptreemux High-speed, flexible, tree-based HTTP router for Go. This is inspired by Julien Schmidt's httprouter, in that it uses a patricia tree, but

Dec 28, 2022
Pure is a fast radix-tree based HTTP router
Pure is a fast radix-tree based HTTP router

package pure Pure is a fast radix-tree based HTTP router that sticks to the native implementations of Go's "net/http" package; in essence, keeping the

Dec 1, 2022
Go HTTP router

violetear Go HTTP router http://violetear.org Design Goals Keep it simple and small, avoiding extra complexity at all cost. KISS Support for static an

Dec 10, 2022
Simple router build on `net/http` supports custom middleWare.

XMUS-ROUTER Fast lightweight router build on net/http supports delegate and in url params. usage : Create new router using NewRouter() which need rout

Dec 27, 2021
Simple HTTP router for Go

ngamux Simple HTTP router for Go Installation Examples Todo Installation Run this command with correctly configured Go toolchain. go get github.com/ng

Dec 13, 2022