New HTTP Router in Go 1.22

February 10, 2024

Introduction

Golang 1.22 was released on February 6, 2024. Quite a lot of updates have been applied to this version. One thing that is quite interesting to me is the enhanced routing pattern. With this feature, we can create dynamic route parameters without the need for 3rd party libraries. Let’s explore further.

Problem

To create an http server in golang is quite easy. For example, as in the code snippet below.

package main

import (
	"fmt"
	"net/http"
)

func IndexHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello from route /index")
}

func main() {
	http.HandleFunc("/index", IndexHandler)

	http.ListenAndServe(":8080", nil)
}

When we go to localhost:8080/index, we will get the response Hello from route /index.

As you can see, there are gotchas and questions in IndexHandler will occur, including:

  1. (1) How to validate HTTP method (without needing to check HTTP method repetitively)?
  2. (2) How to handle dynamic url param cases, such as /index/foo or index/bar?

Solution: Before Go 1.22

To solve this, the easiest solution is to use a community library that available out there. There are quite a lot of golang libraries that support this, such as mux, chi, echo, and many more. For example, in chi, you can do like this:

package main

import (
	"fmt"
	"net/http"

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

func IndexHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello from route /index")
}

func DynamicHandler(w http.ResponseWriter, r *http.Request) {
    param := chi.URLParam(r, "param")
    fmt.Fprintf(w, "Hello from route /index/%s", param)
}

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

    r.Get("/index", IndexHandler)
    r.Get("/index/{param}", DynamicHandler)
    
    http.ListenAndServe(":3000", r)
}

With this:

  1. (1) We dont have to type if r.Method == "GET" in every http handler.
  2. (2) We can get the request url param easily.

Enter Go 1.22

In Go 1.22, we can solve the problem before just with standard library. Here is the example:

package main

import (
	"fmt"
	"net/http"
)

func IndexHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello from route /index")
}

func DynamicHandler(w http.ResponseWriter, r *http.Request) {
	param := r.PathValue("param")
	fmt.Fprintf(w, "Hello from route /index/%s", param)
}

func main() {
	http.HandleFunc("GET /index", IndexHandler)
	http.HandleFunc("GET /index/{param}", DynamicHandler)

	http.ListenAndServe(":8080", nil)
}

As you can see, it looks almost same as the existing library. The difference is that the http method is defined before the url path. To retrieve the value of the dynamic url param, we can use r.PathValue. Be careful for the HTTP method because it is case sensitive, so the http method name must be capitalized (GET, not Get or get).

Conclusion

With this enhanced routing pattern, creating an http server with golang will be easier and more productive, because 2 important features have been provided by the standard library. Of course, if the http server you want to build have complex features, it is better to use the web framework / routing library available in the golang community.

© 2024 Samuel