How To Consume Data From A REST HTTP API With Go

Elliot Forbes Elliot Forbes ⏰ 6 Minutes 📅 Jun 5, 2022

Note - For a tutorial on how to build a RESTful API in Go, click here.

In this tutorial, I’m going to demonstrate how we can consume an already running RESTful API using Go. There are currently hundreds of open REST APIs out there that are just waiting to be consumed and turned into something more meaningful. Todd Motto has put together quite an active repo on GitHub that lists all the public APIs available for consumption. He has categorized them so that we can easily drill down to what we want to check out. You can find that here.

For the purpose of this tutorial, I feel that we should use an API that is already live and that we can easily test in our browser. We’ll be using the very popular pokeapi, which is an API that exposes all the known information related to Pokemon. A bit silly, I know, but it’s a fully-fledged API that follows standard naming conventions and requires no authentication, so it’s easy to get started.

Prerequisites

  • You will need Go version 1.11+ installed on your development machine.

Querying The API

To get us started, we are going to query for all the Pokemon from the original series. We’ll be hitting the http://pokeapi.co/api/v2/pokedex/kanto/ API endpoint. If you navigate to this endpoint in your browser, you should see a huge JSON string printed out. This is the response we’ll be expecting when our Go program performs a GET request on this endpoint.

Note - When you open a web page in a browser, you are performing a GET request for that page.

Making A Simple HTTP GET Request in Go

To mimic what we’ve just done in the browser using Go, we’ll have to write a program that looks like this:

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
)

func main() {
    response, err := http.Get("http://pokeapi.co/api/v2/pokedex/kanto/")

    if err != nil {
        fmt.Print(err.Error())
        os.Exit(1)
    }

    responseData, err := ioutil.ReadAll(response.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(responseData))

}

Within our main function, we first query our API endpoint using http.Get("http://pokeapi.co/api/v2/pokedex/kanto/"). We store the results in either response or err and then check if err is nil. If it is not, we exit.

Below this, we convert the response’s body from bytes into something meaningful that can be printed out in the console. We first use ioutil.ReadAll(response.Body) to read in data from the incoming byte stream and then convert this []byte response into a string using string(responseData) within our print statement.

If you run the above program, you should see that it successfully performs a GET request on our API endpoint and then prints all of our Pokemon in the console.

Creating a Pokemon Struct

Note: This section requires some understanding of how to pase JSON in Go

By knowing the structure of the JSON response that the above API endpoint gives us, we can now map this into a series of structs that we can map our objects to. Below you’ll see the condensed version of the JSON. Within our JSON response, we have a couple of key-value pairs, the first of which is the name of the region where the original Pokemon reside. region gives us a link to the API for gaining more information on that particular region, etc.

The one we are most interested in is pokemon_entries, which stores an array of all the Pokemon that we want.

{
  "name":"kanto",
  "region": {
    "url":"http:\/\/pokeapi.co\/api\/v2\/region\/1\/",
    "name":"kanto"
  },
  "version_groups":[ ... ],
  "is_main_series":true,
  "descriptions":[ ... ],
  "pokemon_entries":[
    {
      "entry_number": 1,
      "pokemon_species": {
        "url":"http:\/\/pokeapi.co\/api\/v2\/pokemon-species\/1\/",
        "name":"bulbasaur"
      }
    }
    ...
  ]
}

In Go, we could map this out into three different structs: a Response struct that contains an array of Pokemon, a Pokemon struct to map these individual Pokemon, and a PokemonSpecies struct to access each Pokemon’s name.

// A Response struct to map the Entire Response
type Response struct {
    Name    string    `json:"name"`
    Pokemon []Pokemon `json:"pokemon_entries"`
}

// A Pokemon Struct to map every Pokemon to.
type Pokemon struct {
    EntryNo int            `json:"entry_number"`
    Species PokemonSpecies `json:"pokemon_species"`
}

// A struct to map our Pokemon's Species, which includes its name
type PokemonSpecies struct {
    Name string `json:"name"`
}

Unmarshalling our JSON

Now that we’ve defined these structs, we can unmarshal the returned JSON string into a new variable. We can do this in our main function by adding these three lines below where we print out our responseData.

var responseObject Response
json.Unmarshal(responseData, &responseObject)

fmt.Println(responseObject.Name)
fmt.Println(len(responseObject.Pokemon))

Note You will also need to add encoding/json to the list of imports at the top of your main.go file.

In the above code, we declare a new responseObject variable, which is of type Response. We then unmarshal our responseData into this object. To test that it all works, we print out our responseObject.Name, which should equal kanto. We then print out the length of our Pokemon array to see if it matches our expectations. If it prints out 151, then we know we’ve done it right and we can now iterate over these Pokemon.

Listing All Our Pokemon

To list all of our Pokemon, we need to create a for loop that iterates over every object in our responseObject’s Pokemon array like so:

var responseObject Response
json.Unmarshal(responseData, &responseObject)

fmt.Println(responseObject.Name)
fmt.Println(len(responseObject.Pokemon))

for _, pokemon := range responseObject.Pokemon {
  fmt.Println(pokemon.Species.Name)
}

Running this now, you should see that every Pokemon’s name is listed in your console.

Full Source Code

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
)

// A Response struct to map the Entire Response
type Response struct {
    Name    string    `json:"name"`
    Pokemon []Pokemon `json:"pokemon_entries"`
}

// A Pokemon Struct to map every Pokemon to.
type Pokemon struct {
    EntryNo int            `json:"entry_number"`
    Species PokemonSpecies `json:"pokemon_species"`
}

// A struct to map our Pokemon's Species, which includes its name
type PokemonSpecies struct {
    Name string `json:"name"`
}

func main() {
    response, err := http.Get("http://pokeapi.co/api/v2/pokedex/kanto/")
    if err != nil {
        fmt.Print(err.Error())
        os.Exit(1)
    }

    responseData, err := ioutil.ReadAll(response.Body)
    if err != nil {
        log.Fatal(err)
    }

    var responseObject Response
    json.Unmarshal(responseData, &responseObject)

    fmt.Println(responseObject.Name)
    fmt.Println(len(responseObject.Pokemon))

    for i := 0; i < len(responseObject.Pokemon); i++ {
        fmt.Println(responseObject.Pokemon[i].Species.Name)
    }

}

Summary

In this tutorial, we’ve looked at how you can perform GET requests on HTTP endpoints and print out the plain text of the response. We’ve then looked at how you can unmarshal the JSON response into struct objects that we can effectively work with as if they were normal objects.

Note - If you found this tutorial useful or require any further help, please let me know in the comments section below. If you think anything is missing, then please feel free to make the changes yourself by submitting a pull request here: tutorialedge.net