Learn Golang Chi to Master the Craft of Web Services


GO

Author: Tuan Nguyen
Reviewer: Deepak Prasad

Introduction to Golang Chi

Welcome to the world of Chi in Golang! If you're delving into web development using Go, Chi is an essential library that you'll find remarkably helpful. But what exactly is Chi, and why should it be a tool in your Golang toolkit?

Chi is a lightweight yet powerful routing library that simplifies the creation of RESTful applications in Go. It excels in its simplicity, performance, and flexibility, providing you with essential features such as URL parameters, middleware, HTTP method routing, and more, all bundled in an uncomplicated package.

Choosing Chi for your Golang projects comes with a multitude of benefits. First and foremost, Chi is expressive. It allows you to construct HTTP routes with ease, making your code not just functional but also clear and easy to understand. If you aim to build applications with structured routing and want to manage middleware effectively, Golang Chi might be the perfect choice. It adheres closely to the "net/http" library, ensuring that as a developer, you have a consistent and familiar experience.

In simple terms, Golang Chi is like a trustworthy guide that efficiently navigates the vast landscape of web development in Go, ensuring that you reach your destination—building powerful, maintainable, and high-performance web applications—with precision and ease.

 

Basic Concepts and Configuration

In this section, we delve deep into the fundamental concepts that make Golang Chi a powerful router for building web applications. Understanding these basics will provide a strong foundation to leverage the full capabilities of Chi in your projects.

 

Routers and Routing

Routing is at the core of any web application, and Golang Chi excels in this aspect. A router is responsible for directing incoming HTTP requests to the appropriate handlers based on the URL path and HTTP method. Chi’s router is designed to be lightweight and fast, focusing on essential features required for dynamic routing.

Let's look at an example:

package main

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

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

	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Welcome to Golang Chi!"))
	})

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

In this simple example, the Golang Chi router directs an HTTP GET request made to the root ("/") URL path to a specific handler function, sending a welcome message in response.

 

Middlewares

Middlewares in Golang Chi are powerful tools that allow you to execute code before or after your HTTP handlers run. They are used for a multitude of common web application tasks such as logging, authentication, and even modifying request/response objects.

Here's a middleware example:

func LoggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Logged connection from %s", r.RemoteAddr)
		next.ServeHTTP(w, r)
		log.Printf("Finished handling request")
	})
}

// Using the middleware in the router
r.Use(LoggingMiddleware)

In this snippet, the LoggingMiddleware logs the details of incoming requests. When incorporated into the Chi router, it will run before each handler, demonstrating the middleware concept in Golang Chi.

 

URL Parameters

URL parameters are a way to capture values from the URL path, making your routes dynamic. Golang Chi makes capturing and using these parameters straightforward.

Here is how you can work with URL parameters:

r.Get("/hello/{name}", func(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, " + chi.URLParam(r, "name")))
})

This route will respond to paths like "/hello/John" with a message "Hello, John", demonstrating the use of URL parameters in Golang Chi.

 

Building RESTful Services

In this section, we will explore how to build RESTful services using Golang Chi. Chi makes it seamless to create various HTTP endpoints, handle URL queries, and manage CRUD operations, which are fundamental in RESTful services.

 

Creating Endpoints (GET, POST, PUT, DELETE)

Golang Chi simplifies the process of setting up various HTTP method routes such as GET, POST, PUT, and DELETE. These endpoints are essential for performing CRUD operations in RESTful services.

  • GET: Retrieve data
  • POST: Create new data
  • PUT: Update existing data
  • DELETE: Remove data
package main

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

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

	// GET endpoint
	r.Get("/users/{userId}", func(w http.ResponseWriter, r *http.Request) {
		userId := chi.URLParam(r, "userId")
		w.Write([]byte("Retrieve user: " + userId))
	})

	// POST endpoint
	r.Post("/users", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Create a new user"))
	})

	// PUT endpoint
	r.Put("/users/{userId}", func(w http.ResponseWriter, r *http.Request) {
		userId := chi.URLParam(r, "userId")
		w.Write([]byte("Update user: " + userId))
	})

	// DELETE endpoint
	r.Delete("/users/{userId}", func(w http.ResponseWriter, r *http.Request) {
		userId := chi.URLParam(r, "userId")
		w.Write([]byte("Delete user: " + userId))
	})

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

This example illustrates how Golang Chi enables the creation of endpoints corresponding to various HTTP methods essential in RESTful services.

 

Handling URL Queries

URL queries are essential for passing non-hierarchical data in the URL. Golang Chi doesn’t provide built-in functions specifically for URL queries, but you can easily access them using the standard net/http package.

r.Get("/search", func(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query().Get("query")
	w.Write([]byte("Search query: " + query))
})

In this example, if you access "/search?query=example", the application will respond with "Search query: example", showcasing how to handle URL queries in a Golang Chi application.

 

Middleware in Chi

Middleware plays a critical role in web development using Golang Chi. It allows for the interception and manipulation of HTTP requests and responses, enabling functionalities such as logging, authentication, and more. Let's delve into the usage, creation, and chaining of middleware in Golang Chi.

Using Existing Middlewares

Golang Chi comes with a variety of built-in middlewares that are readily available for common tasks. These pre-built middlewares can be effortlessly integrated into your Chi router to enhance functionality.

Example: Using a logger middleware

package main

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

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

	// Applying the logger middleware
	r.Use(middleware.Logger)

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

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

In this Golang Chi example, the logger middleware is applied, which logs details about each request to the console.

Creating Custom Middlewares

You might often need to create custom middlewares tailored to your application's needs. Golang Chi makes this process straightforward.

Example: Creating a simple authentication middleware

func SimpleAuthMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		apiKey := r.Header.Get("X-API-KEY")
		if apiKey != "secret" {
			http.Error(w, "Forbidden", http.StatusForbidden)
			return
		}
		next.ServeHTTP(w, r)
	})
}

// Using the custom middleware
r.Use(SimpleAuthMiddleware)

This Golang Chi middleware checks for a specific API key in the request headers and responds with a forbidden status if it's incorrect.

Middleware Chaining

Middleware chaining in Golang Chi allows applying multiple middlewares in a specific order. Each middleware in the chain can process parts of the request/response lifecycle.

Example: Chaining logger and authentication middlewares

r := chi.NewRouter()

// Chaining middlewares
r.Use(middleware.Logger)
r.Use(SimpleAuthMiddleware)

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

In this example, the Golang Chi router applies both the logger and the custom authentication middleware. The logger middleware will execute before the authentication middleware, demonstrating middleware chaining.

 

Handling Requests and Responses

Managing requests and responses effectively is pivotal in any web application built using Golang Chi. It involves dealing with request payloads, crafting responses, and proficiently handling errors and status codes. Let’s unravel these concepts with detailed examples.

Request Payloads

Handling request payloads involves retrieving and processing the data sent by clients to your server.

Example: Handling JSON Payloads

package main

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

type User struct {
	Name  string `json:"name"`
	Email string `json:"email"`
}

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

	r.Post("/users", func(w http.ResponseWriter, r *http.Request) {
		var user User
		json.NewDecoder(r.Body).Decode(&user)
		w.Write([]byte("Added user: " + user.Name))
	})

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

In this Golang Chi example, a JSON payload containing user information is decoded and processed.

Sending Responses

Crafting and sending back responses in Golang Chi involves setting appropriate status codes, headers, and the response body.

Example: Sending a JSON Response

r.Get("/greet", func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{"message": "Hello, Golang Chi!"})
})

This snippet illustrates sending a JSON response with a proper status code and header using Golang Chi.

Handling Errors and Status Codes

Effective error handling and sending appropriate HTTP status codes are vital for a robust application.

Example: Handling 404 Errors

r.Get("/user/{userID}", func(w http.ResponseWriter, r *http.Request) {
	userID := chi.URLParam(r, "userID")

	if userID == "" {
		http.Error(w, "User ID is required", http.StatusBadRequest)
		return
	}

	// Assuming GetUser returns nil if the user doesn't exist
	user := GetUser(userID)
	if user == nil {
		http.NotFound(w, r)
		return
	}

	// Handle the user found case here
})

In this Golang Chi example, various HTTP status codes are used to handle errors and manage responses effectively.

 

Working with URL Parameters

Utilizing URL parameters proficiently is fundamental when developing web applications using Golang Chi. URL parameters allow for dynamic routing, where parts of a route can vary based on input from the user.

Capturing URL Parameters

Golang Chi simplifies the task of capturing URL parameters in your routes, allowing you to extract variable parts of the URL.

Example: Capturing a single URL parameter

package main

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

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

	r.Get("/users/{userID}", func(w http.ResponseWriter, r *http.Request) {
		userID := chi.URLParam(r, "userID")
		w.Write([]byte("User ID: " + userID))
	})

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

In this Golang Chi example, the application captures a user ID from the URL and responds with it.

Validating and Using URL Parameters

After capturing URL parameters, validating them is essential to ensure they meet expected criteria before processing further.

Example: Validating a URL parameter

r.Get("/articles/{articleID}", func(w http.ResponseWriter, r *http.Request) {
	articleID := chi.URLParam(r, "articleID")
	
	// Validation: Ensure the articleID is numeric
	if _, err := strconv.Atoi(articleID); err != nil {
		http.Error(w, "Invalid Article ID", http.StatusBadRequest)
		return
	}

	w.Write([]byte("Article ID: " + articleID))
})

In this example, the Golang Chi application validates that the captured article ID is numeric and sends an error response if it’s not.

 

Advanced Routing

Advanced routing techniques in Golang Chi offer enhanced organization and flexibility when structuring the routes of your application. Concepts like grouping routes, mounting subrouters, and handling wildcards play an essential role in achieving this.

Grouping Routes

Grouping related routes together can make your code more organized and manageable.

Example: Grouping User Routes

package main

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

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

	r.Route("/users", func(r chi.Router) {
		r.Get("/", ListUsers)         // List users
		r.Post("/", CreateUser)      // Create a new user
		r.Get("/{userID}", GetUser)  // Get a specific user
	})

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

In this Golang Chi example, user-related routes are grouped under the "/users" route, providing a clear structure.

Mounting Subrouters

Subrouters allow you to define a set of routes separately and then mount them on a parent router.

Example: Mounting an Order Subrouter

func OrderRoutes() chi.Router {
	r := chi.NewRouter()
	r.Get("/", ListOrders)         // List orders
	r.Get("/{orderID}", GetOrder)  // Get a specific order
	return r
}

// Mounting the subrouter
r.Mount("/orders", OrderRoutes())

In this Golang Chi example, order routes are defined in a separate function and then mounted on the main router under "/orders".

Handling Wildcards in Routes

Wildcards enable routes to match patterns, providing flexibility in route definition.

Example: Handling Filename Wildcards

r.Get("/files/*", func(w http.ResponseWriter, r *http.Request) {
	filepath := chi.URLParam(r, "*")
	http.ServeFile(w, r, filepath)
})

In this example, the Golang Chi application serves files based on a wildcard pattern, matching any path under "/files".

 

Static Files and File Uploads

Managing static files and handling file uploads are common requirements in web development. Golang Chi simplifies these tasks, allowing for the effortless serving of static files and processing of uploaded files. Let’s explore these concepts with detailed examples.

Serving Static Files

Serving static files such as images, CSS, and JavaScript is essential for most web applications.

Example: Serving Static Files

package main

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

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

	// Serving static files from the "static" directory
	FileServer(r, "/static", http.Dir("static"))

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

// FileServer conveniently sets up a http.FileServer handler to serve
// static files from a http.FileSystem.
func FileServer(r chi.Router, path string, root http.FileSystem) {
	fs := http.StripPrefix(path, http.FileServer(root))

	r.Get(path+"/*", func(w http.ResponseWriter, r *http.Request) {
		fs.ServeHTTP(w, r)
	})
}

In this Golang Chi example, a static file server is set up to serve files from the "static" directory under the "/static" path.

Handling File Uploads

Processing file uploads involves receiving files sent by clients and saving them on the server.

Example: Handling File Uploads

r.Post("/upload", func(w http.ResponseWriter, r *http.Request) {
	// Parsing uploaded file
	file, header, err := r.FormFile("file")
	if err != nil {
		http.Error(w, "Unable to parse file", http.StatusBadRequest)
		return
	}
	defer file.Close()

	// Saving the file
	dst, err := os.Create("/uploads/" + header.Filename)
	if err != nil {
		http.Error(w, "Unable to save file", http.StatusInternalServerError)
		return
	}
	defer dst.Close()

	_, err = io.Copy(dst, file)
	if err != nil {
		http.Error(w, "Unable to save file", http.StatusInternalServerError)
		return
	}

	w.Write([]byte("File uploaded successfully"))
})

This Golang Chi example demonstrates handling file uploads by saving the uploaded file to the server and responding with a success message.

 

Frequently Asked Questions (FAQs)

What is Golang Chi used for?

Golang Chi is a lightweight, fast, and expressive router and middleware framework for building Go HTTP services. It allows developers to create RESTful API routes, manage middleware, and process HTTP requests and responses efficiently.

How do you define a route in Golang Chi?

In Golang Chi, you can define a route by specifying the HTTP method and the path, then associating them with a handler function. For instance, a GET request to fetch user details might involve specifying the "/users/{userID}" path and associating it with a handler that retrieves the user’s information based on the provided userID.

How can you group routes in Golang Chi?

Routes can be grouped in Golang Chi to keep related endpoints organized together. For example, all user-related routes like getting user details, creating a new user, and deleting a user can be grouped under a common path prefix like "/users", making the code more structured and manageable.

How do you serve static files using Golang Chi?

Static files, such as images, CSS, and JavaScript files, can be served using Golang Chi by defining a specific route that matches file paths and then serving those files directly from a designated directory on the server.

How does middleware work in Golang Chi?

Middleware in Golang Chi are special functions that get executed before the main handler function. They can modify the HTTP request, the HTTP response, or execute any other code that needs to run before or after the main handler, such as logging, authentication, and more.

Can you capture URL parameters in Golang Chi?

Yes, Golang Chi allows the capturing of URL parameters directly from the route. These parameters are dynamic parts of the URL that can be accessed and utilized within the handler functions to make routes more flexible and dynamic.

How do you handle request payloads in Golang Chi?

Handling request payloads, such as JSON data sent in a POST or PUT request, involves reading and parsing the request body within the handler function. This parsed data can then be used to perform various operations like creating or updating records in a database.

Is Golang Chi suitable for building RESTful APIs?

Absolutely, Golang Chi is specifically designed with features and conveniences that make it suitable for developing RESTful APIs, including dynamic routing, middleware support, and easy parameter extraction from URLs.

 

Golang chi Practical Examples

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:

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

Throughout this comprehensive guide, we have embarked on a rich learning journey exploring the myriad facets of Golang Chi. The key takeaways begin with understanding the foundational aspects, such as the introduction and basic configuration, where we unveiled the essentials of routers and routing. We also delved deeply into the construction of RESTful services, exploring the creation of various endpoints and handling URL queries, which are instrumental in building dynamic and interactive web services.

The exploration of middleware, a powerful feature in Golang Chi, was particularly enlightening. We learned about utilizing existing middlewares, crafting custom ones, and effectively chaining them to enhance the functionality and manageability of our web services. Concepts such as handling requests and responses, error management, and status codes were also meticulously unraveled, granting us the tools to build resilient and user-friendly APIs.

Another pivotal learning was managing URL parameters, which allowed for the creation of more flexible and dynamic routes. Advanced routing techniques, such as grouping routes and handling wildcards, were also illuminated, enabling a more structured and scalable approach to routing. The handling of static files and file uploads was another essential skill acquired, crucial for the development of comprehensive and functional web applications.

In conclusion, this guide has been instrumental in harnessing the power and flexibility of Golang Chi, equipping us with the knowledge and techniques to craft powerful, efficient, and sophisticated web services.

You can go through following links to further enhance your learning experience

 

Tuan Nguyen

Tuan Nguyen

He is proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

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 send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment