Go & Gorilla/Mux: Everything You Need to Build a Web App
James Reed
Infrastructure Engineer · Leapcell

Introduction
gorilla/mux
is a routing management library in the gorilla Web
development toolkit. The gorilla Web
development package is a toolkit that aids in developing Web
servers in the Go
language. It covers various aspects such as form data processing (gorilla/schema
), websocket
communication (gorilla/websocket
), middleware (gorilla/handlers
), session
management (gorilla/sessions
), and secure cookie
handling (gorilla/securecookie
).
mux
has the following advantages:
- It implements the standard
http.Handler
interface and can be used in combination with thenet/http
standard library. It is very lightweight. - It can match handlers based on the request's hostname, path, path prefix, protocol,
HTTP
headers, query string, andHTTP
method. It also supports custom matching logic. - Variables can be used in hostnames, paths, and request parameters, and regular expressions can be specified for them.
- It can pass parameters to a specified handler to construct a complete
URL
. - It supports route grouping, which is convenient for management and maintenance.
Quick Start
The code in this article uses Go Modules
.
Install the gorilla/mux
library:
go get -u github.com/gorilla/gorilla/mux
Next, we will write a Web
service for managing music information. Each piece of music is uniquely identified by MusicID
.
- Define the structure of music:
type Music struct { MusicID string `json:"music_id"` Name string `json:"name"` Artists []string `json:"artists"` Album string `json:"album"` ReleasedAt string `json:"released_at"` } var ( mapMusics map[string]*Music slcMusics []*Music )
- Define the
init()
function to load data from a file:
func init() { mapMusics = make(map[string]*Music) slcMusics = make([]*Music, 0, 1) data, err := ioutil.ReadFile("../data/musics.json") if err != nil { log.Fatalf("failed to read musics.json:%v", err) } err = json.Unmarshal(data, &slcMusics) if err != nil { log.Fatalf("failed to unmarshal musics:%v", err) } for _, music := range slcMusics { mapMusics[music.MusicID] = music } }
- Define two handler functions, one for returning the entire list and the other for a specific piece of music:
func MusicsHandler(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) enc.Encode(slcMusics) } func MusicHandler(w http.ResponseWriter, r *http.Request) { music, ok := mapMusics[mux.Vars(r)["music_id"]] if!ok { http.NotFound(w, r) return } enc := json.NewEncoder(w) enc.Encode(music) }
- Register the handlers:
func main() { r := mux.NewRouter() r.HandleFunc("/", MusicsHandler) r.HandleFunc("/musics/{music_id}", MusicHandler) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }
The usage of mux
is very similar to that of net/http
. First, call mux.NewRouter()
to create a routing object of type *mux.Router
. The way this routing object registers handlers is exactly the same as that of *http.ServeMux
in the standard library, that is, call the HandleFunc()
method to register a handler function of type func(http.ResponseWriter, *http.Request)
, and call the Handle()
method to register a handler object that implements the http.Handler
interface. Two handler functions are registered above, one for displaying the music information list and the other for displaying the information of a specific piece of music.
Notice that the path /musics/{music_id}
uses a variable. The variable name is specified inside the {}
. It can match a specific part of the path. In the handler function, the routing variables of the request r
can be obtained through mux.Vars(r)
, which returns map[string]string
, and then can be accessed using the variable name, such as the access to the variable music_id
in the MusicHandler
above.
Since *mux.Router
also implements the http.Handler
interface, it can be directly registered as the handler object parameter of http.Handle("/", r)
. Here, the root path /
is registered, which is equivalent to entrusting the handling of all requests to *mux.Router
.
Finally, http.ListenAndServe(":8080", nil)
is still used to start a Web
server and wait for incoming requests.
After running, typing localhost:8080
in the browser will display the music list; typing localhost:8080/musics/[specific MusicID]
will display the detailed information of the corresponding music. From the usage process, we can see that the mux
library is very lightweight and can be well integrated with the standard library net/http
.
We can also use regular expressions to limit the pattern of variables. Suppose MusicID
has a fixed pattern (for example: M001-001
, that is, starting with the letter M
, followed by 3 digits, and then connected by -
and 3 digits, which can be represented by the regular expression M\d{3}-\d{3}
). Add a :
after the variable name to separate the variable and the regular expression:
r.HandleFunc("/musics/{music_id:M\\d{3}-\\d{3}}", MusicHandler)
Flexible Matching Methods
mux
provides a rich way of matching requests. In contrast, net/http
can only specify a specific path, which is a bit clumsy.
- Specify the domain name or subdomain name of the route:
r.Host("musicplatform.com") r.Host("{subdomain:[a-zA-Z0-9]+}.musicplatform.com")
The above routes only accept requests from the domain name musicplatform.com
or its subdomains. Regular expressions can be used when specifying the domain name. The second line of code limits that the first part of the subdomain name must be several letters or numbers.
- Specify the path prefix:
// Only handle requests with the path prefix `/musics/` r.PathPrefix("/musics/")
- Specify the request method:
// Only handle GET/POST requests r.Methods("GET", "POST")
- The protocol used (
HTTP
/HTTPS
):
// Only handle https requests r.Schemes("https")
- Headers:
// Only handle requests where the value of the header X-Requested-With is XMLHTTPRequest r.Headers("X-Requested-With", "XMLHTTPRequest")
- Query parameters (that is, the part after
?
in theURL
):
// Only handle requests where the query parameters contain key=value r.Queries("key", "value")
- Combined conditions:
r.HandleFunc("/", HomeHandler) .Host("musicstore.com") .Methods("GET") .Schemes("http")
In addition, mux
also allows custom matchers. A custom matcher is a function of type func(r *http.Request, rm *RouteMatch) bool
, which determines whether the match is successful according to the information in the request r
. The http.Request
structure contains a lot of information: HTTP
method, HTTP
version number, URL
, headers, etc. For example, if we require only handling requests of HTTP/1.1
, we can write it like this:
r.MatchrFunc(func(r *http.Request, rm *RouteMatch) bool { return r.ProtoMajor == 1 && r.ProtoMinor == 1 })
It should be noted that mux
will match in the order of route registration. Therefore, it is usually recommended to put special routes in front and general routes behind. If it is the other way around, special routes will not be matched:
r.HandleFunc("/specific", specificHandler) r.PathPrefix("/").Handler(catchAllHandler)
Sub-routes
Sometimes, grouping and managing routes can make the program modules clearer and easier to maintain. Suppose the website expands its business and adds information related to different types of music (such as pop, rock, etc.). We can define multiple sub-routes for separate management:
r := mux.NewRouter() ps := r.PathPrefix("/pop_musics").Subrouter() ps.HandleFunc("/", PopMusicsHandler) ps.HandleFunc("/{music_id}", PopMusicHandler) rs := r.PathPrefix("/rock_musics").Subrouter() rs.HandleFunc("/", RockMusicsHandler) rs.HandleFunc("/{music_id}", RockMusicHandler)
Sub-routes are generally limited by path prefixes. r.PathPrefix()
will return a *mux.Route
object. Calling its Subrouter()
method creates a sub-route object *mux.Router
, and then registers handler functions through the HandleFunc/Handle
methods of this object.
Using the way of sub-routes, the routes of each part can also be scattered to their respective modules for loading. Define an InitPopMusicsRouter()
method in the file pop_music.go
to be responsible for registering routes related to pop music:
func InitPopMusicsRouter(r *mux.Router) { ps := r.PathPrefix("/pop_musics").Subrouter() ps.HandleFunc("/", PopMusicsHandler) ps.HandleFunc("/{music_id}", PopMusicHandler) }
Define an InitRockMusicsRouter()
method in the file rock_music.go
to be responsible for registering routes related to rock music:
func InitRockMusicsRouter(r *mux.Router) { rs := r.PathPrefix("/rock_musics").Subrouter() rs.HandleFunc("/", RockMusicsHandler) rs.HandleFunc("/{music_id}", RockMusicHandler) }
In the main function of main.go
:
func main() { r := mux.NewRouter() InitPopMusicsRouter(r) InitRockMusicsRouter(r) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }
It should be noted that sub-route matching requires including the path prefix, that is, /pop_musics/
can match the PopMusicsHandler
.
Constructing Route URL
We can give a name to a route. For example:
r.HandleFunc("/musics/{music_id}", MusicHandler).Name("music")
There are parameters in the above route. We can pass parameter values to construct a complete path:
fmt.Println(r.Get("music").URL("music_id", "M001-001")) // /musics/M001-001 <nil>
What is returned is a *url.URL
object, and its path part is /musics/M001-001
. This also applies to hostnames and query parameters:
r := mux.Router() r.Host("{name}.musicplatform.com"). Path("/musics/{music_id}"). HandlerFunc(MusicHandler). Name("music") url, err := r.Get("music").URL("name", "user1", "music_id", "M001-001")
All parameters in the path need to be specified, and the values need to meet the specified regular expressions (if any). The running output is:
$ go run main.go http://user1.musicplatform.com/musics/M001-001
You can call URLHost()
to generate only the hostname part and URLPath()
to generate only the path part.
Middleware
mux
defines the middleware type MiddlewareFunc
:
type MiddlewareFunc func(http.Handler) http.Handler
All functions that meet this type can be used as middleware for mux
. The middleware is applied by calling the Use()
method of the routing object *mux.Router
. When writing middleware, the original handler is generally passed in. In the middleware, the original handler function will be called manually, and then general processing logic will be added before and after:
func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.RequestURI) next.ServeHTTP(w, r) }) }
Suppose the website requires login to access music-related pages. We can write a middleware to handle this logic. If the Cookie
does not exist or is illegal, it will be redirected to the login page. After successful login, a Cookie
with the key token
will be generated to indicate successful login:
func login(w http.ResponseWriter, r *http.Request) { // Here, it is assumed that html/template is used to parse the template and display the login page loginTemplate.ExecuteTemplate(w, "login.tpl", nil) } func doLogin(w http.ResponseWriter, r *http.Request) { r.ParseForm() username := r.Form.Get("username") password := r.Form.Get("password") if username != "user" || password != "123456" { http.Redirect(w, r, "/login", http.StatusFound) return } token := fmt.Sprintf("username=%s&password=%s", username, password) data := base64.StdEncoding.EncodeToString([]byte(token)) http.SetCookie(w, &http.Cookie{ Name: "token", Value: data, Path: "/", HttpOnly: true, Expires: time.Now().Add(24 * time.Hour), }) http.Redirect(w, r, "/", http.StatusFound) }
To display the login page, several template
template files are created and parsed using html/template
:
- Login display page:
<!-- login.tpl --> <form action="/login" method="post"> <label>Username:</label> <input name="username"><br> <label>Password:</label> <input name="password" type="password"><br> <button type="submit">Login</button> </form>
- Main page:
<ul> <li><a href="/pop_musics/">Pop Music</a></li> <li><a href="/rock_musics/">Rock Music</a></li> </ul>
- Music list and details page (example):
<!-- pop_musics.tpl --> <ol> {{ range . }} <li> <p>Song Name: <a href="/pop_musics/{{ .MusicID }}">{{ .Name }}</a></p> <p>Release Date: {{ .ReleasedAt }}</p> <p>Artists: {{ range .Artists }}{{ . }}{{ if not $.Last }}, {{ end }}{{ end }}</p> <p>Album: {{ .Album }}</p> </li> {{ end }} </ol>
<!-- pop_music.tpl --> <p>MusicID: {{ .MusicID }}</p> <p>Song Name: {{ .Name }}</p> <p>Release Date: {{ .ReleasedAt }}</p> <p>Artists: {{ range .Artists }}{{ . }}{{ if not $.Last }}, {{ end }}{{ end }}</p> <p>Album: {{ .Album }}</p>
Next, parse the templates:
var ( loginTemplate *template.Template ) func init() { var err error loginTemplate, err = template.New("").ParseGlob("./tpls/*.tpl") if err != nil { log.Fatalf("load templates failed:%v", err) } }
The logic for accessing the corresponding pages:
func PopMusicsHandler(w http.ResponseWriter, r *http.Request) { loginTemplate.ExecuteTemplate(w, "pop_musics.tpl", slcMusics) } func PopMusicHandler(w http.ResponseWriter, r *http.Request) { music, ok := mapMusics[mux.Vars(r)["music_id"]] if!ok { http.NotFound(w, r) return } loginTemplate.ExecuteTemplate(w, "pop_music.tpl", music) }
Execute the corresponding template and pass in the music list or the information of a specific piece of music. Now write a middleware to restrict that only logged-in users can access music-related pages, and redirect unlogged-in users to the login page when they access:
func authenticateMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("token") if err!= nil { // no cookie http.Redirect(w, r, "/login", http.StatusFound) return } data, _ := base64.StdEncoding.DecodeString(cookie.Value) values, _ := url.ParseQuery(string(data)) if values.Get("username")!= "user" || values.Get("password")!= "123456" { // failed http.Redirect(w, r, "/login", http.StatusFound) return } next.ServeHTTP(w, r) }) }
Then, we apply the middleware authenticateMiddleware
(which requires login verification) to the sub-routes of pop music and rock music, while the login sub-route doesn't need it:
func InitPopMusicsRouter(r *mux.Router) { ps := r.PathPrefix("/pop_musics").Subrouter() // Here ps.Use(authenticateMiddleware) ps.HandleFunc("/", PopMusicsHandler) ps.HandleFunc("/{music_id}", PopMusicHandler) } func InitRockMusicsRouter(r *mux.Router) { rs := r.PathPrefix("/rock_musics").Subrouter() // Here rs.Use(authenticateMiddleware) rs.HandleFunc("/", RockMusicsHandler) rs.HandleFunc("/{music_id}", RockMusicHandler) } func InitLoginRouter(r *mux.Router) { ls := r.PathPrefix("/login").Subrouter() ls.Methods("GET").HandlerFunc(login) ls.Methods("POST").HandlerFunc(doLogin) }
Run the program (note the way to run a multi-file program):
$ go run.
When accessing localhost:8080/pop_musics/
, it will be redirected to localhost:8080/login
. Enter the username user
and the password 123456
, and after successful login, the main page will be displayed. Subsequent requests don't need to be verified again as long as the Cookie
is valid.
Conclusion
This article introduces the lightweight and powerful routing library gorilla/mux
. It supports a rich variety of request matching methods, and sub-routes greatly facilitate route management. Since it is compatible with the standard library net/http
, it can be seamlessly integrated into programs using net/http
and make use of the middleware resources written for net/http
. In the next article, we will introduce gorilla/handlers
— some commonly used middleware.
Leapcell: The Best of Serverless Web Hosting
Finally, I recommend a platform that is most suitable for deploying Go services: **Leapcell
🚀 Build with Your Favorite Language
Develop effortlessly in JavaScript, Python, Go, or Rust.
🌍 Deploy Unlimited Projects for Free
Only pay for what you use—no requests, no charges.
⚡ Pay-as-You-Go, No Hidden Costs
No idle fees, just seamless scalability.
🔹 Follow us on Twitter: @LeapcellHQ