Golang chi Tutorial

golang chi is a compact, idiomatic, lightweight router for creating Go HTTP services. It specializes at assisting you in creating sizable REST API services that can be maintained as your project expands and evolves. chi is designed to handle signaling, cancellations, and request-scoped data throughout a handler chain and is based on the new context package introduced in Go 1.7. chi requires Go 1.10 or newer.

 

Golang Chi Installation and Features

Run the below command to install the package:

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

Features

  • Lightweight
  • Fast - benchmarks
  • 100% compatible with net/http - use all http or middleware pkg that is also compatible with net/http
  • Designed for modular/composable APIs - middlewares, inline middlewares, route groups and sub-router mounting
  • Context control - built on new context package
  • Robust - in production with Cloudflare, Heroku,... and many others (see discussion)
  • Doc generation - docgen auto-generates routing documentation from source to JSON or Markdown
  • Go.mod support - as of v5, go.mod support (see CHANGELOG)
  • No external dependencies

 

Example 1: Create a simple HTTP router using chi

In this example, we will create a simple HTTP router with some middleware using chi:

logger: Logger is a middleware that logs the start and end of each request, along with some useful data about what was requested, what the response status was, and how long it took to return. When standard output is a TTY, Logger will print in color, otherwise it will print in black and white. Logger prints a request ID if one is provided.

requestID: RequestID is a middleware that injects a request ID into the context of each request. A request ID is a string of the form "host.example.com/random-0001", where "random" is a base62 random string that uniquely identifies this go process, and where the last number is an atomically incremented request counter.

recover: Recoverer is a middleware that recovers from panics, logs the panic (and a backtrace), and returns a HTTP 500 (Internal Server Error) status if possible. Recoverer prints a request ID if one is provided.

package main

import (
	"net/http"

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

func main() {
	rout := chi.NewRouter()
	rout.Use(middleware.RequestID)
	rout.Use(middleware.Logger)
	rout.Use(middleware.Recoverer)

	rout.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello GolinuxCloud members!"))
	})

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

Output:

Advertisement

You can see that with the above middleware, some information about the request were printed out.

 

Example 2: Create a HTTP router with custom method

In the below example, we will create a http server which accepts some custom method:

package main

import (
	"net/http"

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

func init() {
	chi.RegisterMethod("PUT")
	chi.RegisterMethod("POST")
}

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

	r.Use(middleware.Logger)
	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello golinuxcloud members!"))
	})
	r.MethodFunc("PUT", "/put", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("this is put method"))
	})
	r.MethodFunc("POST", "/post", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("this is post method"))
	})
	r.HandleFunc("/all", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("capturing all standard http methods"))
	})
	http.ListenAndServe(":8080", r)
}

Output:

 

Example 3: Create a HTTP router with custom handler

The below examples create a custom handler for request. The request will send id parameter, if invalid or empty id, return an error. If id is valid, find the person with that id, return the person's name:

package main

import (
	"errors"
	"net/http"
	"strconv"

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

type Handler func(w http.ResponseWriter, r *http.Request) error

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := h(w, r); err != nil {
		// handle returned error here.
		w.WriteHeader(500)
		w.Write([]byte("empty or invalid id"))
	}
}

type Person struct {
	id        int
	name      string
	character string
}

var listPerson = []Person{
	Person{
		id:        1,
		name:      "Slack",
		character: "Harry Potter",
	},
	Person{
		id:        2,
		name:      "Anna",
		character: "John Weasley",
	},
	Person{
		id:        3,
		name:      "Bob",
		character: "Malfoy",
	},
}

func main() {
	r := chi.NewRouter()
	r.Method("GET", "/", Handler(customHandler))
	http.ListenAndServe(":8080", r)
}

func customHandler(w http.ResponseWriter, r *http.Request) error {
	idQuery := r.URL.Query().Get("id")
	if idQuery == "" {
		return errors.New(idQuery)
	}

	id, err := strconv.Atoi(idQuery)
	if err != nil || id > 3 || id < 0 {
		return errors.New(idQuery)
	}

	w.Write([]byte(listPerson[id].name))
	return nil
}

 

Example 4: Build a file server using chi

The server code directory structure:

C:.
|   gochi.go                    //server
|
\---data                         //data folder
        file1.txt
        file2.txt

In the below example, the user will send a request to read a file from server, if file exists, return the content, if not return an error:

package main

import (
	"net/http"
	"os"
	"path/filepath"
	"strings"

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

func main() {
	route := chi.NewRouter()
	route.Use(middleware.Logger)

	// Index handler
	route.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("welcome to file server"))
	})

	// Create a route along /files that will serve contents from the ./data/ folder.
	workDir, _ := os.Getwd()
	filesDir := http.Dir(filepath.Join(workDir, "data"))
	FileServer(route, "/files", filesDir)

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

func FileServer(r chi.Router, path string, root http.FileSystem) {
	if strings.ContainsAny(path, "{}*") {
		panic("FileServer does not permit any URL parameters.")
	}

	if path != "/" && path[len(path)-1] != '/' {
		r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
		path += "/"
	}
	path += "*"

	r.Get(path, func(w http.ResponseWriter, r *http.Request) {
		rctx := chi.RouteContext(r.Context())
		pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
		fs := http.StripPrefix(pathPrefix, http.FileServer(root))
		fs.ServeHTTP(w, r)
	})
}

Output:

 

Summary

chi is simply an HTTP router that allows you to break down request handling into many smaller layers. Many businesses use chi to create REST services for public APIs. However, REST is just a convention for managing state via HTTP, and there are many other pieces that must be written in order to create a complete client-server system or network of microservices.

 

References

https://pkg.go.dev/github.com/go-chi/chi
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods

 

Categories GO

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment

X