Recently I started to learn how to build an API with Gorilla/mux, mostly because I'm interested in backend development, and I am always learning about it.
I found Gorilla/mux and honestly its documentation is very good, well explained.
According to its documentation, Gorilla/mux is:
Package gorilla/mux implements a request router and dispatcher for matching incoming requests to their respective handler.
The name mux stands for "HTTP request multiplexer". Like the standard http.ServeMux, mux.Router matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
It implements the http.Handler interface so it is compatible with the standard http.ServeMux. Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers. URL hosts, paths and query values can have variables with an optional regular expression. Registered URLs can be built, or "reversed", which helps maintaining references to resources. Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
Following its docs I learned to build a simple grocery API, to get a specific grocery and its quantity, all groceries, and its quantities, post a grocery item and update it.
The three of this project look like this:
------groceriesAPI
|---grocery.go
|---handler.go
|---main.go
In grocery.go we define API's model, handler.go the functions that manage the requests and main.go we register our URLs paths.
First, we install Gorilla/mux.
go get -u github.com/gorilla/mux
grocery.go
package main
type Grocery struct {
Name string `json: "name"`
Quantity int `json: "quantity"`
}
Our model is simple, just two fields, Name for groceries names and Quantity for its quantities.
main.go
package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter().StrictSlash(true)
r.HandleFunc("/allgroceries", AllGroceries) // ----> To request all groceries
r.HandleFunc("/groceries/{name}", SingleGrocery) // ----> To request a specific grocery
r.HandleFunc("/groceries", GroceriesToBuy).Methods("POST") // ----> To add new grocery to buy
r.HandleFunc("/groceries/{name}", UpdateGrocery).Methods("PUT")// ----> To update a grocery
r.HandleFunc("/groceries/{name}", DeleteGrocery).Methods("DELETE") // ----> Delete a grocery
log.Fatal(http.ListenAndServe(":10000", r))
In the code above, after we define "r" as our router. In the case above, the method HandleFunc has two arguments, first a URL path and second, a function to handle it, when the first is matched, the second is called. For example, if "/allgroceries" is requested the function AllGroceries will handle it and serve the data that is requested, in this case, all the groceries in a database (we are not using a database in this project).
We have five routes, one to request all the groceries, one to request a grocery by its name, one to add a grocery, one to update a grocery requested by its name, and one to delete a grocery by its name. All of them are using port 10000, if an error occurs it will trigger a message error and stop the program.
Now, we will move to handler.go to define the functions that handlers every request.
handler.go
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/gorilla/mux"
)
var groceries = []Grocery{
{Name: "Almod Milk", Quantity: 2},
{Name: "Apple", Quantity: 6},
}
func AllGroceries(w http.ResponseWriter, r *http.Request) {
fmt.Println("Endpoint hit: returnAllGroceries")
json.NewEncoder(w).Encode(groceries)
}
We define a variable "groceries" and assign it an array with information of two groceries because we are not using a database. When AllGroceries is called, it will return the array with all groceries in it but as json.
func SingleGrocery(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
for _, grocery := range groceries {
if grocery.Name == name {
json.NewEncoder(w).Encode(grocery)
}
}
}
In this function we retrieve the name of the grocery from the route using "mux.Vars()". Then iterate through the slice and return only the requested grocery.
func GroceriesToBuy(w http.ResponseWriter, r *http.Request) {
reqBody, _ := ioutil.ReadAll(r.Body)
var grocery Grocery
json.Unmarshal(reqBody, &grocery)
groceries = append(groceries, grocery)
json.NewEncoder(w).Encode(groceries)
}
In GroceriesToBuy we receive the post request and assign it to reqBody, then we define a variable grocery with Grocery as type. We parse the JSON data post request using the unmarshal method from the JSON package and store it in the grocery variable. After, we append it to groceries.
func DeleteGrocery(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
for index, grocery := range groceries {
if grocery.Name == name {
groceries = append(groceries[:index], groceries[index+1:]...)
}
}
}
DeleteGrocery will delete a grocery if the name path matches one of the groceries in the slice. Then update the slice.
func UpdateGrocery(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
for index, grocery := range groceries {
if grocery.Name == name {
groceries = append(groceries[:index], groceries[index+1:]...)
var updateGrocery Grocery
json.NewDecoder(r.Body).Decode(&updateGrocery)
groceries = append(groceries, updateGrocery)
fmt.Println("Endpoint hit: UpdateGroceries")
json.NewEncoder(w).Encode(updateGrocery)
return
}
}
}
The last function will update a grocery if the name path matches one of the groceries in the slice. It will take the PUT request, decode it and store it in updateGrocery variable. Then it will append it to groceries.
To be honest, I enjoyed learning how to create a Rest API in go. The next step is to add a database like SQLite or PostgreSQL.
If you have any recommendations about other packages, how to improve my code, my English, anything; please leave a comment or contact me through Twitter, LinkedIn.
The complete code is here