Fast Golang Router With Error Handling

Written by vmihailenco | Published 2022/04/18
Tech Story Tags: golang | router | fast-golang-router | error-handling | programming | learning-to-code | learn-programming | software-development

TLDRBunRouter is as fast as httprouter, but supports middlewares, routing rules priority, and error handling.via the TL;DR App

BunRouter is an extremely fast Golang router for Go with unique combination of features:

  • Middlewares allow to extract common operations from HTTP handlers into reusable functions.
  • Error handling allows to further reduce the size of HTTP handlers by handling errors in middlewares.
  • Routes priority enables meaningful matching priority for routing rules: first static nodes, then named nodes, lastly wildcard nodes.
  • net/http compatible API which means using minimal API without constructing huge wrappers that try to do everything: from serving static files to XML generation (for example, gin.Context or echo.Context).

And yes, it is fast - see the benchmark results.

Quickstart

BunRouter uses a slightly enhanced version of http.HandlerFunc that accepts a bunrouter.Request and returns errors that you can handle with middlewares:

import "github.com/uptrace/bunrouter"

router := bunrouter.New(
	bunrouter.Use(reqlog.NewMiddleware()),
)

router.WithGroup("/api", func(g *bunrouter.Group) {
	g.GET("/users/:id", debugHandler)
	g.GET("/users/current", debugHandler)
	g.GET("/users/*path", debugHandler)
})

func debugHandler(w http.ResponseWriter, req bunrouter.Request) error {
	// use req.Request to get *http.Request

	return bunrouter.JSON(w, bunrouter.H{
		"route":  req.Route(),
		"params": req.Params().Map(),
	})
}

But don't worry, BunRouter supports classical HTTP handlers too.

BunRouter supports the following param types in routing rules:

  • :param is a named parameter that matches a single path segment (text between slashes).
  • *param is a wildcard parameter that matches everything and must always be at the end of the route.

Middlewares

Middlewares allow you to extract common functionality into a reusable function, for example, here is how you can write a middleware that logs processed requests:

func middleware(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
    // you can initialize the middleware here

    // Return the middleware.
    return func(w http.ResponseWriter, req bunrouter.Request) error {
        rec := httptest.NewRecorder()

        // Pass the recorder instead of http.ResponseWriter.
        if err := next(rec, req); err != nil {
            fmt.Printf("%s %s failed: %s\n", req.Method, req.Route(), err)
            // Discard the error.
            return nil
        }

        fmt.Printf("%s %s returned %d\n", req.Method, req.Route(), rec.Code)
    }
}

You can then install the middleware like this:

router.Use(middleware).WithGroup(...)

Error handling

As you may have noticed, BunRouter's handlers return errors which you can then handle in middlewares:

func errorHandler(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
	return func(w http.ResponseWriter, req bunrouter.Request) error {
		// Call the next handler on the chain to get the error.
		err := next(w, req)

		switch err := err.(type) {
		case nil:
			// no error
		case HTTPError: // already a HTTPError
			w.WriteHeader(err.statusCode)
			_ = bunrouter.JSON(w, err)
		default:
			httpErr := NewHTTPError(err)
			w.WriteHeader(httpErr.statusCode)
			_ = bunrouter.JSON(w, httpErr)
		}

		return err // return the err in case there other middlewares
	}
}

See error handling for details.

Routes priority

Routing rules have a matching priority that is based on node types and does not depend on routes definition order:

  • Static nodes, for example, /users/

  • Named nodes, for example, :id.

  • Wildcard nodes, for example, *path.

The following routes are sorted by their matching priority from the highest to the lowest:

  • /users/list.
  • /users/:id.
  • /users/*path.

What's next?

To get started, see the documentation and run examples.

BunRouter comes with many plugins including OpenTelemetry instrumentation that enables distributed tracing and metrics.

Using tracing, you can monitor performance using one of the open source tracing tools that work with OpenTelemetry. Many DataDog competitors also support OpenTelemetry.

Besides, you can export metrics to Prometheus and visualize them using Grafana or a popular alternative.


Written by vmihailenco | Uptrace co-founder
Published by HackerNoon on 2022/04/18