09 Jun 2022

go

Restful routing with Chi in Go

Author

Gurleen Sethi

Restful routing with Chi in Go

Getting Started #

In this tutorial you are going to learn how to use Chi router to create HTTP endpoints in Go. We will also take a look at using middleware in Chi and grouping routes.

Installing Chi #

Setup a new Go project using go mod init ... and install Chi using the following command.

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

Writing HTTP Endpoints #

A good thing about Chi is its ease of use and minimal API. Creating an endpoint is as simple as follows.

  1. We start by creating a new router using chi.NewRouter()
router := chi.NewRouter()
  1. We register a new GET endpoint with the path /.
router.Get("/", func(writer http.ResponseWriter, request *http.Request) {
	_, err := writer.Write([]byte("Hello World"))
	if err != nil {
		log.Println(err)
	}
})

Notice that chi takes the standard http.HandlerFunc as the handler function for an endpoint. FYI, http.HandlerFunc underlying type declaration is type HandlerFunc func(ResponseWriter, *Request).

  1. Chi router implements http.Handler interface so you can directly use it with http.ListenAndServe.
err := http.ListenAndServe(":3000", router)
if err != nil {
	log.Println(err)
}

Here is the full working example.

package main

import (
	"github.com/go-chi/chi/v5"
	"log"
	"net/http"
)

func main() {
	// 1. Create a new router
	router := chi.NewRouter()

	// 2. Register an endpoint
	router.Get("/", func(writer http.ResponseWriter, request *http.Request) {
		_, err := writer.Write([]byte("Hello World"))
		if err != nil {
			log.Println(err)
		}
	})

	// 3. Use router to start the server
	err := http.ListenAndServe(":3000", router)
	if err != nil {
		log.Println(err)
	}
}

HTTP Methods #

Writing endpoints with other HTTP methods (POST, PUT, DELETE etc) is exactly like writing the GET request above, chi provides functions for all of them.

router.Put()
router.Post()
router.Delete()
router.Patch()

For endpoints that have a request body, you would parse the body just like you parse with regular Go http package.

Path Parameters #

chi provides a function (chi.URLParam) to get path parameters that are defined using curly brackets { }.

router.Get("/user/{username}", func(writer http.ResponseWriter, request *http.Request) {
	username := chi.URLParam(request, "username") // 👈 getting path param
	_, err := writer.Write([]byte("Hello " + username))
	if err != nil {
		log.Println(err)
	}
})

In this example we define a GET endpoint that has a path parameter username, we call chi.URLParam(request, "username") to get the value of the path parameter.

To get query params from a request you can use the inbuilt Go functionality request.URL.Query().

Middleware with Chi #

Chi lets you to easily add middleware to the router using the use function. The use function takes in another function as a parameter that takes in a http.Handler and returns a http.Handler as well (http.Handler is just an interface with a single function ServeHTTP(ResponseWriter, *Request)).

Here is how you would write a middleware to log each request's path.

// 1. Write a middleware
func Logger(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		log.Println(request.URL.Path)
		handler.ServeHTTP(writer, request)
	})
}

// 2. Register with router
router.Use(Logger)

Here is the full example with logging middleware.

package main

import (
	"github.com/go-chi/chi/v5"
	"log"
	"net/http"
)

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

	router.Use(Logger) // 👈 register middleware with router

	router.Get("/", func(writer http.ResponseWriter, request *http.Request) {
		_, err := writer.Write([]byte("Hello World"))
		if err != nil {
			log.Println(err)
		}
	})

	err := http.ListenAndServe(":3000", router)
	if err != nil {
		log.Println(err)
	}
}

// 👇 a logging middleware
func Logger(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		log.Println(request.URL.Path)
		handler.ServeHTTP(writer, request)
	})
}

Chi provides a wide range of middlewares built into the package itself, you can find the complete list here.

Using the inbuilt logging middleware from Chi:

package main

import (
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware" // 👈 import middleware package
	"log"
	"net/http"
)

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

	router.Use(middleware.Logger) // 👈 use the inbuilt logging middleware

	...
}

Grouping Routes #

Chi provides a couple of ways to group routes.

You can use the chi.Route function to create sub-routes.

router.Route("/users", func(r chi.Router) {
	r.Get("/{id}", getUser)
	r.Post("/", createUser)
	r.Put("/deactivate", deactivateUser)
})

This will add 3 routes to the router GET /users/{id} POST /users/ and PUT /users/deactivate.

You can group routes using chi.Group which allows you to apply middleware to only grouped routes.

router.Post("/login", login)
router.Post("/signup", login)

// Apply auth middleware to only `GET /users/{id}`
router.Group(func(r chi.Router) {
	r.Use(AuthMiddleware)
	r.Get("/users/{id}")
})

Thank you for reading this article 🙏 I hope you learned something new, highly encourage you to read the chi documentation.

Email Newsletter Icon
Don't miss new articles
Get notified once/twice per month when new articles are published.
Table of Contents
TheDeveloperCafe