Fast Golang router with error handling
Tags: webdev, programming, go
Published: 2022-04-17 11:34:08

BunRouter is an extremely fast [Golang router](https://bunrouter.uptrace.dev/guide/golang-router.html) for Go with unique combination of features:

- [Middlewares](#middlewares) allow to extract common operations from HTTP handlers into reusable functions.
- [Error handling](#error-handling) allows to further reduce the size of HTTP handlers by handling errors in middlewares.
- [Routes priority](#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](https://web-frameworks-benchmark.netlify.app/result?l=go).

## Quickstart

BunRouter uses a slightly enhanced version of http.HandlerFunc that accepts a `bunrouter.Request` and returns errors that you can handle with [middlewares](#middlewares):

```go
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](https://bunrouter.uptrace.dev/guide/golang-router.html#compat-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:

```go
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:

```go
router.Use(middleware).WithGroup(...)
```

## Error handling

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

```go
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](https://bunrouter.uptrace.dev/guide/golang-http-error-handling.html) for details.

## Routes priority

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

1. Static nodes, for example, `/users/`
2. Named nodes, for example, `:id`.
3. 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 [Golang HTTP router](https://bunrouter.uptrace.dev/guide/golang-router.html) docs and run [examples](https://github.com/uptrace/bunrouter/tree/master/example).

BunRouter comes with many plugins including [OpenTelemetry](https://uptrace.dev/opentelemetry/) instrumentation that enables [OpenTelemetry tracing](https://uptrace.dev/opentelemetry/distributed-tracing.html) and [OpenTelemetry metrics](https://uptrace.dev/opentelemetry/metrics.html).

Using tracing, you can [monitor performance](https://bunrouter.uptrace.dev/guide/golang-http-performance.html) using one of the [distributed tracing tools](https://uptrace.dev/blog/distributed-tracing-tools.html) that work with OpenTelemetry. Many [DataDog competitors](https://uptrace.dev/blog/datadog-competitors.html) also support OpenTelemetry.

Besides, you can [export metrics to Prometheus](https://uptrace.dev/opentelemetry/prometheus-metrics.html) and visualize them using [Grafana alternatives](https://uptrace.dev/blog/grafana-alternatives.html).