Write simple REST API program in Golang using Gorilla/Mux

In this post, we are creating an API that will allow users to perform CRUD operation on the given data. Before we get begin, I’ll assume that you:

  1. Have Go installed in your system
  2. Basic understanding of Go programming language.
  3. Know how to interact with RESTAPI

Now let’s install gorilla/mux dependency in our working directory

go get -u github.com/gorilla/mux

Once installed, we can start writing our RESTAPI program. For this tutorial, we will be writing our code in single file which is main.go. Also, we will be using sample mock data based on superhero character. There is no need to set up database for this purpose.

First step is to import required package to make our program up and running.

package main

import (
	"encoding/json"
	"log"
	"math/rand"
	"net/http"
	"strconv"

	"github.com/gorilla/mux"
)

Aside from gorilla/mux, other packages are all standard go library.

Next, we will create struct type for our data initialize it as a slice.

type Hero struct {
	ID     string  `json:"id"`
	Area   string  `json:"area"`
	Title  string  `json:"title"`
	Name *Name `json:"name"`
}

type Name struct {
	Firstname string `json:"firstname"`
	Lastname  string `json:"lastname"`
}

var heroes []Hero

Name require additional struct because we need to pass multiple data into it.

Now we can start writing function for each REST component.

To display all heroes when requesting for it, we can write function as below

func getHeroes(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(heroes)
}

w.Header line is required to set header value in JSON format and will be present in every function.

To view single hero, below function can be used.

func getHero(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	params := mux.Vars(r)
	for _, item := range heroes {
		if item.ID == params["id"] {
			json.NewEncoder(w).Encode(item)
			return
		}
	}
	json.NewEncoder(w).Encode(&Hero{})
}

What those code does is it will set params variable to get the id from request then it will loop through our data and find one with the id and display it.

To add new hero to our data, we can use strconv package to generate random id. Do note that this method not suitable for production because it might generate duplicate id for each data.

func createHero(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	var hero Hero
	_ = json.NewDecoder(r.Body).Decode(&hero)
	hero.ID = strconv.Itoa(rand.Intn(100000000))
	heroes = append(heroes, hero)
	json.NewEncoder(w).Encode(hero)
}

To delete data, we are using DELETE method in our function as below.

func deleteHero(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	params := mux.Vars(r)
	for index, item := range heroes {
		if item.ID == params["id"] {
			heroes = append(heroes[:index], heroes[index+1:]...)
			break
		}
	}
	json.NewEncoder(w).Encode(heroes)
}

What it does is it will find the data based on id in the URL and will append the data by deleting it.

Update function is a combination of add and delete functions. The difference is it will not generate random id as the id is permanently set by previous request.

func updateHero(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	params := mux.Vars(r)
	for index, item := range heroes {
		if item.ID == params["id"] {
			heroes = append(heroes[:index], heroes[index+1:]...)
			var hero Hero
			_ = json.NewDecoder(r.Body).Decode(&hero)
			hero.ID = params["id"]
			heroes = append(heroes, hero)
			json.NewEncoder(w).Encode(hero)
			return
		}
	}
}

Now all function has been created, let’s create route endpoints and insert some data to our program in our main package.

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

	// Mock data - In prod, we use db
	heroes = append(heroes, Hero{ID: "1", Area: "Metropolis", Title: "Superman", Name: &Name{Firstname: "Clark", Lastname: "Kent"}})
	heroes = append(heroes, Hero{ID: "2", Area: "Gotham", Title: "Batman", Name: &Name{Firstname: "Bruce", Lastname: "Wayne"}})

	r.HandleFunc("/heroes", getHeroes).Methods("GET")
	r.HandleFunc("/heroes/{id}", getHero).Methods("GET")
	r.HandleFunc("/heroes", createHero).Methods("POST")
	r.HandleFunc("/heroes/{id}", updateHero).Methods("PUT")
	r.HandleFunc("/heroes/{id}", deleteHero).Methods("DELETE")

	log.Fatal(http.ListenAndServe(":8000", r))
}

We are using mux as our router for this program. It will run on port 8000. There are totals of 5 handles for our functions that we just created. Any request will be redirected to its specific function.

To test the program we can use Curl program or Postman app .

You can find the complete code as below.

//main.go
package main

import (
	"encoding/json"
	"log"
	"math/rand"
	"net/http"
	"strconv"

	"github.com/gorilla/mux"
)

type Hero struct {
	ID     string  `json:"id"`
	Area   string  `json:"area"`
	Title  string  `json:"title"`
	Name *Name `json:"name"`
}

type Name struct {
	Firstname string `json:"firstname"`
	Lastname  string `json:"lastname"`
}

var heroes []Hero

func getHeroes(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(heroes)
}

func getHero(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	params := mux.Vars(r)
	for _, item := range heroes {
		if item.ID == params["id"] {
			json.NewEncoder(w).Encode(item)
			return
		}
	}
	json.NewEncoder(w).Encode(&Hero{})
}

func createHero(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	var hero Hero
	_ = json.NewDecoder(r.Body).Decode(&hero)
	hero.ID = strconv.Itoa(rand.Intn(100000000))
	heroes = append(heroes, hero)
	json.NewEncoder(w).Encode(hero)
}

func updateHero(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	params := mux.Vars(r)
	for index, item := range heroes {
		if item.ID == params["id"] {
			heroes = append(heroes[:index], heroes[index+1:]...)
			var hero Hero
			_ = json.NewDecoder(r.Body).Decode(&hero)
			hero.ID = params["id"]
			heroes = append(heroes, hero)
			json.NewEncoder(w).Encode(hero)
			return
		}
	}
}

func deleteHero(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	params := mux.Vars(r)
	for index, item := range heroes {
		if item.ID == params["id"] {
			heroes = append(heroes[:index], heroes[index+1:]...)
			break
		}
	}
	json.NewEncoder(w).Encode(heroes)
}

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

	// Mock data - In prod, we use db
	heroes = append(heroes, Hero{ID: "1", Area: "Metropolis", Title: "Superman", Name: &Name{Firstname: "Clark", Lastname: "Kent"}})
	heroes = append(heroes, Hero{ID: "2", Area: "Gotham", Title: "Batman", Name: &Name{Firstname: "Bruce", Lastname: "Wayne"}})

	r.HandleFunc("/heroes", getHeroes).Methods("GET")
	r.HandleFunc("/heroes/{id}", getHero).Methods("GET")
	r.HandleFunc("/heroes", createHero).Methods("POST")
	r.HandleFunc("/heroes/{id}", updateHero).Methods("PUT")
	r.HandleFunc("/heroes/{id}", deleteHero).Methods("DELETE")

	log.Fatal(http.ListenAndServe(":8000", r))
}