Editor’s note: This post was updated on 15 February 2023 to include information about matching routes, concurrency, as well as HttpRouter.
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, serving single-page applications (SPAs), middleware, handling CORS requests, and testing handlers.
N.B., as of December 2022, the entire Gorilla Web Toolkit was put into archive mode on GitHub. In the words of the project maintainers, this means that:
“All the repositories have gone into “read-only” mode. Anyone still using them can still clone them, go get them, and continue to build projects against them. In effect, there’s really no change here from the last 12 months, and it won’t break existing projects.
…What it does signal is that there will be no future development on these libraries.
…Folks are welcome to (as they always have been) fork them: all of the Gorilla libraries are permissively licensed (MIT, BSD-3, and Apache 2.0).”
You can read further details about why the toolkit was archived here.
This tutorial will walk you through using the Gorilla Mux package as a router for your applications. You’ll learn how to work with Gorilla Mux by using it to build a simple API.
Jump ahead:
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.
Here’s the struct type you’ll use as the data 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 the way frameworks like Fiber do. Instead, in this tutorial you’ll be using the json
package from the standard library 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 Decoder
struct returned by json.NewDecoder
to parse the JSON body of an HTTP 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 Encoder
struct returned by NewEncoder
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") }
Now that you’re familiar with the basics of working with JSON, let’s move on to routing.
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")
Your next task is to create these handler functions and set up a server.
Before you go ahead to create your handlers, let’s pause and take a closer look at how you can match routes with Gorilla Mux. Right now your routing is based on the root of the application and the HTTP request method. For example, the declaration:
router.HandleFunc("/create", create).Methods("POST")
We’ll call the create
handler when a client makes a POST request to the address yourserveraddress.com/create
. However, Gorilla Mux offers ways to be even more precise about which routes your endpoints match.
Let’s assume you wanted to ensure your create
handler only processed requests made over HTTPS. All you’d need is to chain a scheme match to the route declaration, like so:
router.HandleFunc("/create", create).Methods("POST").Schemes("https")
Gorilla Mux provides matches for several other criteria. For example, you can match routes by host URL, like this: router.HandleFunc("/route",
handler).Host("www.yourdomain.com")
. This declaration will only process requests to the URL www.yourdomain.com/route
.
Mux also allows you to:
You can find examples using these matches here. A word of warning about matching routes with Mux: if an incoming request matches two or more route declarations (there’s a route conflict), the request will always be processed using the first route declaration.
Because applying the same matches to several different routes is tedious and can make your code harder to read, Gorilla Mux provides a feature called subrouting. This feature allows you to create a router that will apply a set of matches to all routes you register with it. Here’s an example:
router := mux.NewRouter() s := router.Host("www.yourdomain.com").Headers("Connection", "Keep-Alive").Subrouter() s.HandleFunc("/create", handler) s.HanldeFunc("/update", updateHandler)
The /create
and /update
routes will now only be processed if the request has the appropriate Host
and Headers
. Now that you understand how route matching works, you can move on to creating your handlers.
Your handler functions are where you’ll declare the business logic for your application. Depending on the operation, your handlers will need a writer
object (to write a response to the client) and a request
object (to obtain information about the incoming request).
These objects will typically be an instance of the http.ResponseWriter
and/or *http.Request
types, respectively. Here’s a skeletal example of a typical handler function that returns JSON responses to the client:
func example(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Content-Type", "application/json") }
Your next step is to create the data store thatyou’ll use for this tutorial:
var BioData = make([]Bio, 0)
The BioData
variable above is a slice of the Bio
type you defined earlier. This data store is sufficient for this tutorial, but your projects will likely have more sophisticated needs that require you to use a database. If you aren’t familiar with working with databases in Go, you can check out the tutorials on MongoDB and the GORM ORM.
Now that your data store is in place, you can begin creating your request
handlers. The create
handler function is assigned to process POST
requests, so its business logic will save the new JSON object in the request
body to the application’s data store. Here’s the code for the create
function:
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 ends by writing the human
struct as a response to the client.
Next up is the read
handler. The read
handler function is assigned to GET
requests; thus, its business logic will fetch data from the data store and return matching 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") name := 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 works by reading the name
parameter of the request using the Vars
method of the mux
package. It then loops through the BioData
slice you’re using as a data store, and returns the struct that matches the name
parameter to the client as JSON.
The update
handler function is assigned to PUT
requests, so its business logic should update the struct in the BioData
data store specified by the request:
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 finally appends the human
struct from the request body to the BioData
slice.
The delete_
handler function is assigned to DELETE
requests; thus, its business logic will delete a specified struct from the data store:
func delete_(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Content-Type", "application/json") name := 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.
Now that you’ve set up your handler functions, the last step is to set up the server that will listen for requests.
You can set up a server using the ListenAndServe
method of the http
package. The ListenAndServe
method takes as arguments the port you want the server to run on and a router instance, if any. Here’s a server for your application:
func RunServer() { 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 RunServer
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 building apps with Gorilla Mux!
Using Gorilla Mux doesn’t have much impact on the concurrency of your application compared to using the standard library’s ServeMux. Each handler function will still be called in a separate goroutine when processing an incoming request.
So what does this mean for you? It means you have to be careful when modifying data that’s shared between handlers, so you avoid race conditions. The easiest way to implement this is usually to avoid having any shared data in the first place and use only local variables. However, if shared data is something you can’t avoid, be sure to guard all access to the data with a Mutex or some other synchronization mechanism.
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 by 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.
HttpRouter is a fast, minimal router for Go applications that’s been around for a long time. It’s so fast and reliable that it’s used as the router of several Go web frameworks, the most popular of those being Gin. Here’s what the docs say about HttpRouter:
HttpRouter is a lightweight high performance HTTP request router (also called multiplexer or just mux for short) for Go.
In contrast to the default mux of Go’s
net/http
package, this router supports variables in the routing pattern and matches against the request method. It also scales better.The router is optimized for high performance and a small memory footprint. It scales well even with very long paths and a large number of routes. A compressing dynamic trie (radix tree) structure is used for efficient matching
To give you a feel for how it works, here’s a tiny demo of routing with HttpRouter:
import ( "github.com/julienschmidt/HttpRouter" "log" "net/http" ) func main() { router := HttpRouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ HttpRouter.Params) { w.Write([]byte("Hello from http router")) }) log.Fatal(http.ListenAndServe(":8080", router)) }
Just like with Chi, in this demo the main
function starts up a server that listens on port 8080
and writes a hello response to the client. To help you choose the best router for your needs, here’s a comparison between HttpRouter and Mux:
Metric | Gorilla Mux | HttpRouter |
---|---|---|
Speed | Fast, see benchmarks | Very fast, see benchmarks |
Documentation Generation | No | No |
Popularity | 17k stars, used by 77k projects on GitHub | 15k stars, used by 16k projects on GitHub |
Rendering | Yes | Yes |
MiddleWare | Yes | Yes |
WebSockets | Yes | No |
Testing | Yes | Yes |
Gorilla Mux and HttpRouter are both excellent routers. HttpRouter has the advantage of speed and minimalism, but Mux offers more flexibility and functionality.
In this tutorial, you learned about the Gorilla Mux, HttpRouter, and Chi router packages, how to route and build APIs with the Gorilla Mux router, and how to evaluate the three packages to help you make better decisions for your projects.
Check out how these Go frameworks can help you build web applications faster.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.