Paul Akinyemi I'm a fullstack web developer and technical writer with experience in JavaScript and Python. I love bringing pretty and complex user interfaces to life with clean and efficient code.

An intro to routing in Go with Gorilla Mux

5 min read 1658

An Intro To Routing In Go With Gorilla Mux

HTTP routers are tools and libraries that help receive and relay network requests to the specified handlers. HTTP routers run on servers, intercept incoming requests, and designate requests to specified handler functions.

Routers vary between backend frameworks; most backend frameworks ship with routers and many other functionalities for building software faster.

The Gorilla Mux package is one of the most popular routers and projects in the Go ecosystem, used in popular projects like Geth because of the package’s diverse features. Gorilla Mux provides functionalities for matching routes, serving static files, building single-page applications (SPAs), middleware, handling CORS requests, and testing handlers.

This tutorial will walk you through using the Gorilla Mux package as a router for your applications. You’ll learn how to use Gorilla Mux by building a simple API with it.

To jump ahead in this article:

Getting started with Gorilla Mux

Gorilla Mux

Once you’ve set up your Go workspace, run this command in your working directory to install the Gorilla Mux package:

go get -u github.com/gorilla/mux

After installing the Gorilla Mux package, import the packages and modules that you’ll be using in this tutorial at the top of your Go file, like so:

import (
        "encoding/json"
        "github.com/gorilla/mux"
        "log"
        "net/http"
)

Gorilla Mux depends on the standard http package, and you’ll use the http package in many parts of this tutorial, including setting up a server. You’ll use the json package to encode and decode structs to JSON and vice versa.

Parsing structs into JSON in Go

Here’s the struct you’ll use as the model in this tutorial:



type Bio struct {
        Name string `json:"name"`
        Age  int    `json:"age"`
}

Gorilla Mux doesn’t provide functionality for parsing structs to JSON as frameworks like Fiber does. You can use the json package to decode JSON requests and encode structs as JSON responses to clients.

Here’s an example of encoding and decoding with the json package.
Start by creating the Go variable you want to decode:

var human Bio

The human variable is an instantiation of the Bio struct. You can use the Decode method of the NewDecoder method to parse the body of a request into the initialized struct.

 err := json.NewDecoder(request.Body).Decode(&human)
        if err != nil {
                log.Fatalln("There was an error decoding the request body into the struct")
        }

Similarly, you can use the Encode method of the NewEncoder method to write a struct that will be encoded as JSON to the client.

err = json.NewEncoder(writer).Encode(&human)
        if err != nil {
                log.Fatalln("There was an error encoding the initialized struct")
}

Routing with the Gorilla Mux package

You can create a router instance with the NewRouter method, like so:

router := mux.NewRouter()

After declaring a new router instance, you can use the HandleFunc method of your router instance to assign routes to handler functions along with the request type that the handler function handles. Here’s an example:

router.HandleFunc("/api/v1/example", exampleHandler).Methods("GET")

The HandleFunc method assigned the api/v1/example route to the exampleHandler handler function to handle GET requests.

These are the router declarations for the endpoints of the CRUD API you’ll be building in this tutorial:

router.HandleFunc("/create", create).Methods("POST")
router.HandleFunc("/read", read).Methods("GET")
router.HandleFunc("/update", update).Methods("PUT")
router.HandleFunc("/delete", delete_).Methods("DELETE")

Next, you’ll create these handler functions and set up a server.

Setting up handler functions

The handler function is where you’ll declare the business logic for your application. Depending on the operation, you’ll be using a writer and request an instance of the http.ResponseWriter and/or *http.Request types, respectively.

func example(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "application/json")
}

The response content type is set to JSON in the example handler function above.


More great articles from LogRocket:


var BioData = make([]Bio, 0)

The BioData variable above is the slice we will use as a data store in this tutorial. For your projects, you can check out these tutorials on MongoDB and the GORM ORM for how to use a database with Go.

The create handler function was assigned as a POST request, so the business logic will save the request body to a data store.

func create(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "application/json")
        writer.WriteHeader(http.StatusOK)
        var human Bio
        err := json.NewDecoder(request.Body).Decode(&human)
        if err != nil {
                log.Fatalln("There was an error decoding the request body into the struct")
        }
        BioData = append(BioData, human)
        err = json.NewEncoder(writer).Encode(&human)
        if err != nil {
                log.Fatalln("There was an error encoding the initialized struct")
        }

}

The create handler function writes the StatusOk header to the client on request receipt, decodes the JSON request body into the human struct instance, saves the human struct to the BioData slice, and writes the human struct back to the client.

The read handler function was assigned to a GET request; thus, the business logic will fetch data from the data store and return the data to the client based on the client’s request.

func read(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "application/json")
                params := mux.Vars(request)["name"]
        for _, structs := range BioData {
                if structs.Name == name {
                        err := json.NewEncoder(writer).Encode(&structs)
                        if err != nil {
                                log.Fatalln("There was an error encoding the initialized struct")
                        }
                }
        }

}

The read function reads the name parameter of the request using the Vars method of the mux package, loops through the data store, and returns the struct that matches the name query to the client as JSON.

The update handler function was assigned to the PUT request so that the business logic would update a struct in the BioData data store.

func update(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "application/json")
        var human Bio
        err := json.NewDecoder(request.Body).Decode(&human)
        if err != nil {
                log.Fatalln("There was an error decoding the request body into the struct")
        }
        for index, structs := range BioData {
                if structs.Name == human.Name {
                        BioData = append(BioData[:index], BioData[index+1:]...)
                }
        }
        BioData = append(BioData, human)
        err = json.NewEncoder(writer).Encode(&human)
        if err != nil {
                log.Fatalln("There was an error encoding the initialized struct")
        }
}

The update function parses the JSON in the request body into the human variable, loops through the BioData slice, deletes the entry if it exists, and appends the human struct from the request body.

The delete_ handler function was assigned the DELETE request; thus, the business logic would delete a struct from the data store.

func delete_(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "application/json")
        params := mux.Vars(request)["name"]
        indexChoice := 0
        for index, structs := range BioData {
                if structs.Name == name {
                        indexChoice = index
                }
        }
        BioData = append(BioData[:indexChoice], BioData[indexChoice+1:]...)
}

The delete_ function retrieves the name parameter from the request, loops through the BioData data store, and deletes the entry if it exists.

After setting up handler functions, the next step is to set up a server that listens for requests.

Setting up a server

You can set up a server using the ListenAndServe method of the http package. The ListenAndServe method takes in the port for the server and the router instance, if any.

func Run() {
        router := mux.NewRouter()
        router.HandleFunc("/create", create).Methods("POST")
        router.HandleFunc("/read", read).Methods("GET")
        router.HandleFunc("/update", update).Methods("PUT")
        router.HandleFunc("/delete", delete_).Methods("DELETE")

        err := http.ListenAndServe(":8080", router)
        if err != nil {
                log.Fatalln("There's an error with the server," err)
        }

}

Calling the Run function in the main function of your project should start up a server on the local host port 8080. And that’s all you need to know to get started with Gorilla Mux!

Gorilla Mux router vs. Chi router

Chi Router

Chi is a lightweight, composable router for building HTTP services in Go. You’ll find the Chi router useful for building large RESTful API services you want to maintain and support over time. Heroku, Cloudflare, and 99designs use the Chi router in production.

Chi is built on the context package, making it suitable for handling signaling, cancellation, and request-scoped operations across handler chains. The Chi package also contains sub-packages for middleware and generating documentation, and a rendering package for managing HTTP requests and response payloads.

Here’s a quick example of routing with the Chi router.

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

func main() {
        router := chi.NewRouter()
        router.Use(middleware.Logger)
        router.Get("/", func(writer http.ResponseWriter, request *http.Request) {
                writer.Write([]byte("welcome to the chi"))
        })
        http.ListenAndServe(":3000", router)
}

The main function starts up a server that listens on port 3000 and writes a string to the client as a response.

When you start a new project, you might wonder which router to use. Here’s a comparison between the two router packages to help you decide based on what you’re building.

Metric Gorilla Mux Chi Router
Speed Fast, see benchmarks Fast, see benchmarks
Documentation Generation No Yes
Popularity 17k stars, used in 77k projects on GitHub 12k stars, used by 11k projects on GitHub
Rendering Yes Yes
MiddleWare Yes Yes
WebSockets Yes Yes
Testing Yes Yes

The Gorilla Mux and Chi router are both great at routing, but you’ll find most Go developers use Gorilla Mux because it’s older and because there are more learning resources for Gorilla Mux.

Conclusion

In this tutorial, you learned about the Gorilla Mux and Chi router packages, how to route and build APIs with the Gorilla Mux router, and read an overview of the two packages to help you make better decisions for your projects.

Check out how these Go frameworks can help you build web applications faster.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Paul Akinyemi I'm a fullstack web developer and technical writer with experience in JavaScript and Python. I love bringing pretty and complex user interfaces to life with clean and efficient code.

Leave a Reply