michi is a routing library designed for the Enhanced ServeMux in Go 1.22
Published: 2024-03-23 07:03:04

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mco66k3hs0ig9fipqpf.jpeg)

I released michi. michi is a routing library designed for the Enhanced ServeMux in Go 1.22.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i9v0c29snnfqqkkyr2vd.png)

https://github.com/go-michi/michi

## Sample
https://go.dev/play/p/wBWzrwkVD5j


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7v9mwc37j91687hk6p33.png)



## Features

- **True 100% compatible with net/http** - **http.ServerMux**, http.Handler and http.HandlerFunc
- **Enhanced http.ServeMux** - [After Go 1.22](https://github.com/golang/go/issues/61410), it is possible to use http method and path values
- **API like chi** - Route, Group, With and [chi middlewares](https://github.com/go-chi/chi?tab=readme-ov-file#middlewares)
- **No external dependencies** - Only use standard package
- **Lightweight** - Only 160 lines. michi only supports to setup handlers, routing is delegated to http.ServeMux
- **Performance** - michi == http.ServeMux


## About michi(道)
- michi(道) means routes in Japanese.

## Background

In versions before Go 1.21, net/http package didn't let you set HTTP methods or get path values from URL. This had to be done inside the handler, as seen here.

```go
mux := http.NewServeMux()
// HTTP method and PathValue specification are not possible at handler setup
mux.Handle("/user/", func(w http.ResponseWriter, r *http.Request) {
	// Filtering by HTTP method must be implemented inside the handler
	if r.Method != http.MethodGet {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	// Implementing PathValue inside the handler
	// GET /user/12345 -> Hello 12345
	values := strings.Split(r.URL.Path, "/") // This is a simplified implementation
	w.Write([]byte("Hello " + values[len(values)-1]))
})
```

To solve this, people used routing libraries like chi and gorilla/mux, and web frameworks such as echo and gin.

```go
// chi
r := chi.NewRouter()
// HTTP method and PathValue can be specified at handler setup
r.Get("/user/{id}", func(w http.ResponseWriter, r *http.Request) {
	// Implementing HTTP method and PathValue inside the handler is unnecessary
	// GET /user/12345 -> Hello 12345
	w.Write([]byte("Hello " + chi.URLParam(r, "id")))
}) 
Go 1.22 solves this problem by allowing the specification of HTTP methods and retrieval of PathValues.goCopy codemux := http.NewServeMux()
mux.HandleFunc("GET /user/{id}", func(w http.ResponseWriter, r *http.Request) {
	// Implementing HTTP method and PathValue inside the handler is unnecessary
	// GET /user/12345 -> Hello 12345
	w.Write([]byte("Hello " + r.PathValue("id")))
})
```

With the new features in http.ServeMux, it's easier to switch from chi to http.ServeMux. But, chi has easy-to-use interfaces for handlers and middleware that http.ServeMux doesn't have.

```go
r := chi.NewRouter()
// Setting middleware with the Use method
r.Use(middleware.Logger)
// Nesting routes with the Route method
r.Route("/user", func(r chi.Router) {
	// Setting middleware specific to /user
	r.Use(middleware.UserLogger)
	r.Get("/{id}", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello " + r.PathValue("id")))
	})
}
```

michi was created to solve it. You get http.ServeMux's routing from the standard library and chi's ease of use.

In michi, as shown in the diagrams, routing uses http.ServeMux from the net/http package, not a custom implementation.

<img src="https://raw.githubusercontent.com/go-michi/michi/assets/michi-architecture.png" />
<img src="https://raw.githubusercontent.com/go-michi/michi/assets/chi-architecture.png" />

## Usage

```bash
go get -u github.com/go-michi/michi
```

```go
package main

import (
	"fmt"
	"net/http"

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

func main() {
	rt := michi.NewRouter()
	// Compatible with chi's interface, allowing the use of chi's middleware
	rt.Use(middleware.Logger)
	rt.Route("/user", func(rt michi.Router) {
		rt.HandleFunc("GET /{id}", func(w http.ResponseWriter, r *http.Request) {
			// GET /user/12345 -> Hello 12345
			w.Write([]byte("Hello " + r.PathValue("id")))
		})
	}
	http.ListenAndServe(":3000", rt)
}
```

Before using michi, make sure to read the http.ServeMux documentation.
https://pkg.go.dev/net/http#ServeMux

For more detailed michi usage, check the GoDoc examples.
https://pkg.go.dev/github.com/go-michi/michi

## differences betwean http.ServeMux and chi
### 1. Register method for HTTP Method

http.ServeMux and michi don't have register method for HTTP Method. use http.ServeMux handler rules.

```go
// chi
r.Get("/user/{id}", userHandler)

// michi
r.HandleFunc("GET /user/{id}", userHandler)
```

Chi might support it in the future. It seems to depend on the number of requests.
https://github.com/go-chi/chi/issues/895

### 2. Get Path Value

http.ServeMux and michi get path value from http.Request PathValue method.

```go
// chi
id := chi.URLParam(r, "id")

// michi
id := r.PathValue("id")
```

Chi will also support PathValue, so this difference will disappear.
https://github.com/go-chi/chi/issues/873

### 3. Handler pattern match rule
- registers `/a/` handler and requests `/a/b`
  - http.ServeMux and michi exec `/a/` handler
    - prefix match
    - `/a/b` matches `/a/`.
  - chi returns `404 Not Found`
    - exact match
    - `/a/b` does not match `/a/`.
- after Go 1.22, registers `/a/{$}` and requests `/a/b`
  - http.ServeMux and michi returns `404 Not Found`
    - exact match
    - `/a/b` does not match `/a/`.

### 4. Pattern match of value
Chi allows for pattern specification in ways that http.ServeMux and michi do not, such as:

- Regular Expressions
  - /date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}
  - Example: /date/2017/04/01
- Partial Matches
  - /user-{userID}
  - Example: /user-12345
- Multiple Values
  - /{yyyy}-{mm}-{dd}
  - Example: /2024-10-10

### 5. Mount sub router
- http.ServeMux requires adding sub router using Handle method because of it does not have a Mount method.
- Handle method can't omit a parent handler path.
```go
aRouter := http.NewServeMux()
// can't omit /a/ path
aRouter.HandleFunc("/a/hello", handler("hello"))
rt := http.NewServeMux()
rt.Handle("/a/", aRouter)
```

- chi adding sub router using Mount, it can omit a parent path.
```go
aRouter := chi.NewRouter()
// omit /a/ path
aRouter.HandleFunc("/hello", handler("hello"))
rt := chi.NewRouter()
rt.Mount("/a", aRouter)
```

- michi is the sames as http.ServeMux.
```go
aRouter := michi.NewRouter()
// can't omit /a/ path
aRouter.HandleFunc("/a/hello", handler("hello"))
rt := michi.NewRouter()
rt.Handle("/a/", aRouter)
```

or using Route

```go
func main() {
    r := michi.NewRouter()
    // can't omit /a/ path
    r.Route("/a", func(r michi.Router) {
        r.HandleFunc("/hello", handler("hello"))
    })
}
```


# Conclusion
Michi offers a balance between the enhanced functionality of Go 1.22's http.ServeMux and the convenience of chi. While I prefer using standard libraries where possible, chi still has advantages in convenience, making a transition to michi not always necessary. Choose based on your dependency and convenience requirements.


# Credits
- [Peter Kieltyka](https://github.com/pkieltyka) for https://github.com/go-chi/chi
  - michi's middleware interface from chi.