RESTful APIs using Golang and Gorilla Mux for beginners

Subscribe to our newsletter and never miss any upcoming articles

Listen to this article

In this article, we're going to create a simple REST API with complete CRUD functionality using Golang. Also, we will be using Gorilla Mux router for routing purposes. goraster-1024x640.png What is Golang?

Golang is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson.

It is syntactically similar to C, but with memory safety, exceptional garbage collection, structural typing, and concurrency.

The language is often referred to as Golang because of its domain name, golang.org, but the proper name is Go.

What are we building?

we are building an API that will allow us to create, update, delete and view the coding courses.

INSTALLATION:

If you are reading this article, then you might be having Golang installed on your machine already, but in case if you don't, go ahead and download it from golang.org/dl.

For a complete and in-depth installation guide, watch the following video. This article is mainly focused on designing RESTful APIs using Go and not the installation.

Installation of golang - Hitesh Choudhary

I would suggest Windows users to use git bash as their terminal because windows cmd is not that efficient. If you don't have git installed on your machine, go ahead and download it from git-scm.com/downloads.

LET'S CREATE OUR FOLDER STRUCTURE:

In golang, it is not necessary to create a folder structure. However, it's a good habit to organize your code as if you will publish it someday.

Open up your git bash and run following command in it:

mkdir -p $GOPATH/src/github.com/<your_github_username>/restapi

Now, if you go to C:\Users\USERNAME\go\src\github.com\<your_github_username> you should see a folder named restapi. That's where we have to store our code files.

If you don't find C:\Users\USERNAME\go that means, you have to set up your GOPATH. Run the following command in you git bash to do that:

export GOPATH=$HOME/go

and again run following command:

mkdir -p $GOPATH/src/github.com/<your_github_username>/restapi

It will create a restapi folder in C:\Users\USERNAME\go\src\github.com\<your_github_username>

This should be your folder structure:

C:/
  - USERNAME/
      - go/
          - src/
              - github.com/
                  - <your_github_username>/
                      - restapi/

Go ahead and open the restapi folder in VS code.

MAKE GIT BASH THE DEFAULT TERMINAL OF VS CODE:

Instead of using git bash externally, you can make git bash as "the default terminal" of your VS code. Follow these two steps: (git should be installed in your machine)

Click on Select Default Shell as shown below: Screenshot (27).png It will pop up a list of available terminals. Select Git Bash from it, as shown below:

Screenshot (28).png

LET'S DESIGN THE REST APIS:

Create a new file named main.go.

If you are in VS code, as soon as you create main.go, you will get a pop up to install some packages related to go. So, go ahead and click on install all. It will take some time to install those. (ignore if you've already installed them)

Install Gorilla Mux Router by running the following command:

go get -u github.com/gorilla/mux

In main.go initialize the main package and import necessary packages, including the gorilla mux:

package main

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

    "github.com/gorilla/mux"
)

Let's create a main function. In that, initialize the mux router and the server using net/http package that we've imported.

func main() {
    //Initialize router using mux
    router := mux.NewRouter()

    // Create the routes, we will be creating each function in future
    router.HandleFunc("/api/courses", getCourses).Methods("GET")
    router.HandleFunc("/api/course/{id}", getSingleCourse).Methods("GET")
    router.HandleFunc("/api/courses/create", createCourse).Methods("POST")
    router.HandleFunc("/api/courses/update/{id}", updateCourse).Methods("PUT")
    router.HandleFunc("/api/courses/delete/{id}", deleteCourse).Methods("DELETE")

    // Initialize a server, log.Fatal will throw an error if anything goes wrong
    log.Fatal(http.ListenAndServe(":8000", router))

}

Let's create our models outside the main function using Struct, which will hold information about our data (It's like a model which we define in Django/Express)

// Author model (Struct)
type Author struct {
    Firstname string `json:"firstname"`
    Lastname  string `json:"lastname"`
}

// Course model (Struct)
type Course struct {
    ID     string  `json:"id"`
    Name   string  `json:"name"`
    Price  string  `json:"price"`
    Link   string  `json:"link"`
    Author *Author `json:"author"` // This field is pointing towards the Author struct
}

If you take a look at the above code, in Course Struct we are defining a field Name of a type string, which will appear as name when we get a JSON response. That's how we are mapping fields in the struct to hold our data.

What is Struct in Go?

Structs are collections of fields with each field having its type. They’re useful for grouping data together, to form records like we've done here to group our Course and Author models.

LET'S ADD A DUMMY DATA WHICH WILL ACT AS A DATABASE:

// Initialize courses variable as a slice of type struct Course (which nothing but an Array data structure)
var courses []Course

func main() {
    //Initialize router
    router := mux.NewRouter()

    // Dummy data of the books, as we don't have a database (slice)
    // we are just appending a Course struct (which we are creating on the spot)
    // in 'courses' slice (array) that we've defined earlier
    // syntax --> append(<where you want to append>,<what you want to append>)
    courses = append(courses, Course{ID: "124134", Name: "FullStack Django Developer Freelance ready", Price: "299", Link: "https://courses.learncodeonline.in/learn/FullStack-Django-Developer-Freelance-ready",
        Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})
    courses = append(courses, Course{ID: "154434", Name: "Full stack with Django and React", Price: "299", Link: "https://courses.learncodeonline.in/learn/Full-stack-with-Django-and-React",
        Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})
    courses = append(courses, Course{ID: "198767", Name: "Complete React Native bootcamp", Price: "199", Link: "https://courses.learncodeonline.in/learn/Complete-React-Native-Mobile-App-developer",
        Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})

    // Handel the routes (when you are making a request from postman, make sure that you are hitting correct url, even missing or extra "/" can give an error
    router.HandleFunc("/api/courses", getCourses).Methods("GET")
    router.HandleFunc("/api/course/{id}", getSingleCourse).Methods("GET")
    router.HandleFunc("/api/courses/create", createCourse).Methods("POST")
    router.HandleFunc("/api/courses/update/{id}", updateCourse).Methods("PUT")
    router.HandleFunc("/api/courses/delete/{id}", deleteCourse).Methods("DELETE")

    // Initialize a server
    log.Fatal(http.ListenAndServe(":8000", router))

}

LET'S CREATE THE FUNCTIONS FOR EACH ROUTE:

Function to get all the Courses:

// function to get all the books
// this is identical to app.get("/",(req,res)=>{}) in the express.js
func getCourses(res http.ResponseWriter, req *http.Request) {
    // we have to set the header "Content-Type: application/json"
    // because we are sending JSON data with a request through postman
    res.Header().Set("Content-Type", "application/json")
    // we are taking variable 'courses' in which we've appended dummy data and returning that as a response
    json.NewEncoder(res).Encode(courses)
}

This is getCourses function, which we're executing when the user hits localhost:8000/api/courses.

Run the server by running the following commands: (Whenever we make changes in our code, we have to kill the existing server and run these two commands)

go build
#go build will create .exe file of name restapi
./restapi

If there is nothing in the terminal, that means our server is running. So, let's see the response of the above function:

Set Content-Type as application/json in Postman's Headers as shown below (This header is important when we are sending POST and PUT requests):

go2.PNG Let's hit localhost:8000/api/courses and see what we get:

gores1.PNG

As you can see, we've got all the courses (in JSON format) from our dummy data.

Function to get a single Course:

// function to get a single course
func getSingleCourse(res http.ResponseWriter, req *http.Request) {
    // set the header "Content-Type: application/json"
    res.Header().Set("Content-Type", "application/json")
    params := mux.Vars(req) // we are extracting 'id' of the Course which we are passing in the url
    // we are looping through courses and
    // if a course with ID == 'id' that we've passed in url
    // we are encoding that course only
    for _, item := range courses {
        if item.ID == params["id"] {
            json.NewEncoder(res).Encode(item) // sending matched course in json format
            return
        }
    }
    // if we don't get any matching course we will simply return a message
    json.NewEncoder(res).Encode("No course found")
}

This is getSingleCourse function, which we are executing when the user hits localhost:8000/api/course/{id}

As we have changed our code, we have to kill the existing server and run the server again by running the following commands:

go build
#go build will create .exe file of name restapi
./restapi

Let's hit localhost:8000/api/course/{id} and see what we get:

gores2.PNG As you can see, after hitting localhost:8000/api/course/124134 it returned course with an id 124134. Let's see if we hit the url with the wrong id what we get!

gores5.PNG It returned "No course found".

Function to create a Course:

func createCourse(res http.ResponseWriter, req *http.Request) {
    // set the header "Content-Type: application/json"
    res.Header().Set("Content-Type", "application/json")
    // asign a course variable of type Course struct
    var course Course
    // take everyhing that is coming from postman
    // which is nothing but req.Body. Store the decoded req.Body in 'course' variable
    _ = json.NewDecoder(req.Body).Decode(&course)
    course.ID = strconv.Itoa(rand.Intn(1000000)) // just creating dummy id as we are not sending an id from postman - not safe for production
    // Now, we will simply append that course with decoded req.Body in our
    // courses slice (Array) which we have defined] earlier
    courses = append(courses, course)
    // At the end, send the course that we have created.
    json.NewEncoder(res).Encode(course)
}

This is createCourse function, which we are executing when the user hits localhost:8000/api/courses/create with POST request.

Kill the existing server and run the server again:

go build && ./restapi

This is a POST request, Which means we are going to send some JSON data to create a course. So, let's hit localhost:8000/api/courses/create with POST request and see what we get:

gores4.PNG Now, let's hit localhost:8000/api/courses to get all the courses and see if we get the newly created course in the response:

gores6.PNG Here it is!

Function to delete a Course:

func deleteCourse(res http.ResponseWriter, req *http.Request) {
    // set the header "Content-Type: application/json"
    res.Header().Set("Content-Type", "application/json")
    params := mux.Vars(req) // we are extracting 'id' of the Course which we are passing in the url

    // looping through all the courses we have
    // then compare each course's ID with the id that we've passed in the url (params)
    for i, item := range courses {
        if item.ID == params["id"] {
            // This is slicing of a slice(array) 
            // append all the courses in `courses` slice (array) except, the one which has ID equal to the id which we've passed in the url
            // for example, if ID of the course, whose index is "3" is equal to the id we've passed in the url.
            // we will take items from index "0-2" and "4-`last index`" i.e. except index "3" from `courses` slice(array)
            // and will store that slice(array) in courses itself
            courses = append(courses[:i], courses[i+1:]...)
            break
        }
    }
    json.NewEncoder(res).Encode(courses) // it will return all the other courses except the deleted one.
}

This is deleteCourse function, which we are executing when the user hits localhost:8000/api/courses/delete/{id} with DELETE request.

Kill the existing server and run the server again:

go build && ./restapi

Let's hit localhost:8000/api/courses/delete/{id} with DELETE request and see what we get:

gores7.PNG Now, let's hit localhost:8000/api/courses to get all the courses and see if we get the deleted course in the response:

gores8.PNG

Function to update a Course:

func updateCourse(res http.ResponseWriter, req *http.Request) {
    // set the header "Content-Type: application/json"
    res.Header().Set("Content-Type", "application/json")
    params := mux.Vars(req) // we are extracting 'id' of the Course which we are passing in the url

    // this method is bit cheeky because we don't have a database
    for i, item := range courses {
        if item.ID == params["id"] {
            // this is the same slicing technique that we used to delete the course in deleteCourse() func
            courses = append(courses[:i], courses[i+1:]...)
            var course Course
            // take data which is coming from postman
            // and create new course from it
            _ = json.NewDecoder(req.Body).Decode(&course)
            course.ID = params["id"] // we are keeping the id same because we are updating the existing course and not creating new one - not safe for production
            // Now, append that course in 'courses' slice(array)
            courses = append(courses, course)
            json.NewEncoder(res).Encode(course)
            return
        }
    }
}

This is updateCourse function, which we are executing when the user hits localhost:8000/api/courses/update/{id} with PUT request.

Kill the existing server and run the server again:

go build && ./restapi

Let's hit localhost:8000/api/courses/update/{id} with PUT request and see what we get:

gores9.PNG We got the response with changed name and changed price as we expected.

Now, let's hit localhost:8000/api/courses to get all the courses and see if we get the updated course in the response:

gores10.PNG

HERE IS FULL main.go FILE:

package main

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

    "github.com/gorilla/mux"
)

// Author model (Struct)
type Author struct {
    Firstname string `json:"firstname"`
    Lastname  string `json:"lastname"`
}

// Course model (Struct)
type Course struct {
    ID     string  `json:"id"`
    Name   string  `json:"name"`
    Price  string  `json:"price"`
    Link   string  `json:"link"`
    Author *Author `json:"author"`
}

// Initialize Course variable as slice (which nothing but an Array data structure)
var courses []Course

// function to get all the books
func getCourses(res http.ResponseWriter, req *http.Request) {\
    res.Header().Set("Content-Type", "application/json")
    json.NewEncoder(res).Encode(courses)
}

// function to get a single course
func getSingleCourse(res http.ResponseWriter, req *http.Request) {
    res.Header().Set("Content-Type", "application/json")
    params := mux.Vars(req) 
    for _, item := range courses {
        if item.ID == params["id"] {
            json.NewEncoder(res).Encode(item) // we are sending matched course in json format
            return
        }
    }
    json.NewEncoder(res).Encode("No course found")

}

func createCourse(res http.ResponseWriter, req *http.Request) {
    res.Header().Set("Content-Type", "application/json")
    var course Course
    _ = json.NewDecoder(req.Body).Decode(&course)
    course.ID = strconv.Itoa(rand.Intn(1000000)) 
    courses = append(courses, course)
    json.NewEncoder(res).Encode(course)
}

func updateCourse(res http.ResponseWriter, req *http.Request) {
    res.Header().Set("Content-Type", "application/json")
    params := mux.Vars(req) // we are extracting 'id' of the Course which we are passing in the url

    for i, item := range courses {
        if item.ID == params["id"] {
            courses = append(courses[:i], courses[i+1:]...)
            var course Course
            _ = json.NewDecoder(req.Body).Decode(&course)
            course.ID = params["id"] 
            courses = append(courses, course)
            json.NewEncoder(res).Encode(course)
            return
        }
    }
}

func deleteCourse(res http.ResponseWriter, req *http.Request) {
    res.Header().Set("Content-Type", "application/json")
    params := mux.Vars(req) // we are extracting 'id' of the Course which we are passing in the url

    for i, item := range courses {
        if item.ID == params["id"] {
            courses = append(courses[:i], courses[i+1:]...)
            break
        }
    }
    json.NewEncoder(res).Encode(courses) // it will return all the other courses except the deleted one.
}

func main() {
    //Initialize router
    router := mux.NewRouter()

    // Dummy data of books as we don't have a database (slice)
    courses = append(courses, Course{ID: "124134", Name: "FullStack Django Developer Freelance ready", Price: "299", Link: "https://courses.learncodeonline.in/learn/FullStack-Django-Developer-Freelance-ready",
        Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})
    courses = append(courses, Course{ID: "154434", Name: "Full stack with Django and React", Price: "299", Link: "https://courses.learncodeonline.in/learn/Full-stack-with-Django-and-React",
        Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})
    courses = append(courses, Course{ID: "198767", Name: "Complete React Native bootcamp", Price: "199", Link: "https://courses.learncodeonline.in/learn/Complete-React-Native-Mobile-App-developer",
        Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})

    // Handel the routes
    router.HandleFunc("/api/courses", getCourses).Methods("GET")
    router.HandleFunc("/api/course/{id}", getSingleCourse).Methods("GET")
    router.HandleFunc("/api/courses/create", createCourse).Methods("POST")
    router.HandleFunc("/api/courses/update/{id}", updateCourse).Methods("PUT")
    router.HandleFunc("/api/courses/delete/{id}", deleteCourse).Methods("DELETE")

    // Initialize a server
    log.Fatal(http.ListenAndServe(":8000", router))

}

With this, we come to an end of this article on Building REST API with Golang.

If you face any problem during this project, you can go ahead and check out the code on my Github.

Also, make sure to subscribe to our newsletter on blog.learncodeonline.in and never miss any upcoming articles related to programming just like this one.

I hope this post will help you in your journey. Keep learning!

My LinkedIn and GitHub .

Comments (2)

Amal Shaji's photo

Why create a workspace under GOPATH/src when you can use Go Modules?

Shubham Waje's photo

I wasn't planning to add any project structure as this was a basic project. So, I went with GOPATH instead of Go modules.

But yes, you are right! modules are dramatically faster than GOPATH, and one should always consider them over GOPATH. Thank you for addressing.