Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Create A Simple RESTful API With Golang

TwitterFacebookRedditLinkedInHacker News

Most modern applications have separation between the backend and the frontend layers. The backend is typically a RESTful API and is critical part of full stack development. These APIs are generally further broken down into a collection of routes, often referred to as endpoints. Building applications like this is often very clean and maintainable in comparison to mashing everything into a single application.

I have been creating RESTful APIs with a variety of programming languages, for example Node.js and Java, but lately I’ve been doing a lot of research on the Go programming language. It is fast and very solid programming language that every seems to be talking about. Because of this it only made sense to see what it took to build a RESTful API with Go, often referred to as Golang.

We’re going to see what it takes to build a simple API that does basic CRUD operations using the Go programming language.

For simplicity, we’re not going to be using a database in this example. We’re going to be hard-coding some data which can be referred to as mock data. This will allow us to focus on core material like defining endpoints and doing basic operations.

At this point we’re going to assume Go is already installed and configured on your machine. From the Terminal (Mac and Linux) or Command Prompt (Windows), execute the following:

mkdir -p $GOPATH/src/github.com/nraboy/restapi

If your command line doesn’t support any of the above, just manually create the directory any way you see fit.

In the freshly created project, you’ll want to create a file that contains our main function. In this example we’ll be using $GOPATH/src/github.com/nraboy/restapi/myproject.go.

Inside this Golang file we want to add the following code. It is a lot of code, but don’t worry, we’re going to break it down next.

package main

import (
    "encoding/json"
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

type Person struct {
    ID        string   `json:"id,omitempty"`
    Firstname string   `json:"firstname,omitempty"`
    Lastname  string   `json:"lastname,omitempty"`
    Address   *Address `json:"address,omitempty"`
}

type Address struct {
    City  string `json:"city,omitempty"`
    State string `json:"state,omitempty"`
}

var people []Person

func GetPersonEndpoint(w http.ResponseWriter, req *http.Request) {
    params := mux.Vars(req)
    for _, item := range people {
        if item.ID == params["id"] {
            json.NewEncoder(w).Encode(item)
            return
        }
    }
    json.NewEncoder(w).Encode(&Person{})
}

func GetPeopleEndpoint(w http.ResponseWriter, req *http.Request) {
    json.NewEncoder(w).Encode(people)
}

func CreatePersonEndpoint(w http.ResponseWriter, req *http.Request) {
    params := mux.Vars(req)
    var person Person
    _ = json.NewDecoder(req.Body).Decode(&person)
    person.ID = params["id"]
    people = append(people, person)
    json.NewEncoder(w).Encode(people)
}

func DeletePersonEndpoint(w http.ResponseWriter, req *http.Request) {
    params := mux.Vars(req)
    for index, item := range people {
        if item.ID == params["id"] {
            people = append(people[:index], people[index+1:]...)
            break
        }
    }
    json.NewEncoder(w).Encode(people)
}

func main() {
    router := mux.NewRouter()
    people = append(people, Person{ID: "1", Firstname: "Nic", Lastname: "Raboy", Address: &Address{City: "Dublin", State: "CA"}})
    people = append(people, Person{ID: "2", Firstname: "Maria", Lastname: "Raboy"})
    router.HandleFunc("/people", GetPeopleEndpoint).Methods("GET")
    router.HandleFunc("/people/{id}", GetPersonEndpoint).Methods("GET")
    router.HandleFunc("/people/{id}", CreatePersonEndpoint).Methods("POST")
    router.HandleFunc("/people/{id}", DeletePersonEndpoint).Methods("DELETE")
    log.Fatal(http.ListenAndServe(":12345", router))
}

So what is happening in the above code?

The first thing you’ll notice is that we’re importing various dependencies. We’ll be working with JSON data so the encoding/json dependency is required. While we’ll be working with HTTP requests, the net/http dependency is not quite enough. The mux dependency is a helper to not only make endpoints easier to create, but also give us more features. Since this is an external dependency, it must be downloaded like follows:

go get github.com/gorilla/mux

More information on mux can be found in the official documentation.

With the dependencies imported we want to create the struct objects that will house our data. The data we plan to store will be people data:

type Person struct {
    ID        string   `json:"id,omitempty"`
    Firstname string   `json:"firstname,omitempty"`
    Lastname  string   `json:"lastname,omitempty"`
    Address   *Address `json:"address,omitempty"`
}

You’ll notice we are defining what properties exist in the struct, but we are also defining tags that describe how the struct will appear as JSON. Inside each of the tags there is an omitempty parameter. This means that if the property is null, it will be excluded from the JSON data rather than showing up as an empty string or value.

Inside the Person struct there is an Address property that is a pointer. This will represent a nested JSON object and it must be a pointer otherwise the omitempty will fail to work. So what does Address look like?

type Address struct {
    City  string `json:"city,omitempty"`
    State string `json:"state,omitempty"`
}

Again, this is a nested structure that is not too different from the previous.

Because we’re not using a database, we want to create a public variable that is global to the project. This variable will be a slice of Person and contain all the data used in this application.

Our easiest endpoint is probably the GetPeopleEndpoint because it will only return the full person variable to the frontend. Where things start to change is when we want to insert, delete, or get a particular record.

func GetPersonEndpoint(w http.ResponseWriter, req *http.Request) {
    params := mux.Vars(req)
    for _, item := range people {
        if item.ID == params["id"] {
            json.NewEncoder(w).Encode(item)
            return
        }
    }
    json.NewEncoder(w).Encode(&Person{})
}

In the above GetPersonEndpoint we are trying to get a single record. Using the mux library we can get any parameters that were passed in with the request. We then loop over our global slice and look for any ids that match the id found in the request parameters. If a match is found, use the JSON encoder to display it, otherwise create an empty JSON object.

In reality, your endpoints will communicate with a database and will probably involve a lot less application logic. This is because you’ll be using some form of querying instead.

The CreatePersonEndpoint is a bit different because we’ll be receiving JSON data to work with in the request.

func CreatePersonEndpoint(w http.ResponseWriter, req *http.Request) {
    params := mux.Vars(req)
    var person Person
    _ = json.NewDecoder(req.Body).Decode(&person)
    person.ID = params["id"]
    people = append(people, person)
    json.NewEncoder(w).Encode(people)
}

In the above we decode the JSON data that was passed in and store it in a Person object. We assign the new object an id based on what mux found and then we append it to our global slice. In the end, our global array will be returned and it should include everything including our newly added piece of data.

In the scenario of this example application, the method for deleting data is a bit different as well.

func DeletePersonEndpoint(w http.ResponseWriter, req *http.Request) {
    params := mux.Vars(req)
    for index, item := range people {
        if item.ID == params["id"] {
            people = append(people[:index], people[index+1:]...)
            break
        }
    }
    json.NewEncoder(w).Encode(people)
}

In this case, we have the DeletePersonEndpoint looping through the data similarly to the GetPersonEndpoint we saw earlier. The difference is that instead of printing the data, we need to remove it. When the id to be deleted has been found, we can recreate our slice with all data excluding that found at the index.

Finally we end up in our runnable main function that brings the application together.

func main() {
    router := mux.NewRouter()
    people = append(people, Person{ID: "1", Firstname: "Nic", Lastname: "Raboy", Address: &Address{City: "Dublin", State: "CA"}})
    people = append(people, Person{ID: "2", Firstname: "Maria", Lastname: "Raboy"})
    router.HandleFunc("/people", GetPeopleEndpoint).Methods("GET")
    router.HandleFunc("/people/{id}", GetPersonEndpoint).Methods("GET")
    router.HandleFunc("/people/{id}", CreatePersonEndpoint).Methods("POST")
    router.HandleFunc("/people/{id}", DeletePersonEndpoint).Methods("DELETE")
    log.Fatal(http.ListenAndServe(":12345", router))
}

In the above code we first create our new router and add two objects to our slice to get things started. Next up we have to create each of the endpoints that will call our endpoint functions. Notice we are using GET, POST, and DELETE where appropriate. We are also defining parameters that can be passed in.

At the very end we define that our server will run on port 12345, which at this point brings our project to a close. Run the project and give it a shot. You might need a tool like Postman or cURL to test all the endpoints.

Conclusion

You just saw how to build a very simple RESTful API using the Go programming language. While we used mock data instead of a database, we saw how to create endpoints that do various operations with JSON data and Golang slices. By creating an API your application will become easy to maintain and be expandable to web, mobile and IoT as each of these platforms will only require a different frontend that we had not created in this example.

A video version of this article can be seen below.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.