Attempting to Learn Go - REST API and A Bit On Templates

shindakun

Steve Layton

Posted on December 16, 2018

Attempting to Learn Go - REST API and A Bit On Templates

RESTy

Previously, we went looked at how to send an email through the MailGun API. This time around we'll be updated the code we put together a couple weeks back to pull in a set of users from the jsonplaceholder site. Once updated we'll see if we can make use of it, if you read the last post you might be able to guess what we're going to do...


The core approach is very similar to last time we did this. We have a known format for the JSON object we'll be receiving from the endpoint so we'll start by creating our struct or more correctly - structs. I think we'll break it up into smaller pieces, you can see below while it does have everything we need it's a bit unwieldy I think. To be clear we don't actually need to populate the entire struct with the incoming JSON. We could just get the ID, Name, Username, and Email and that will take care of it. I'm only including everything as an example, the code sample on GitHub likely won't though.

type Users []struct {
  ID       int    `json:"id"`
  Name     string `json:"name"`
  Username string `json:"username"`
  Email    string `json:"email"`
  Address  struct {
    Street  string `json:"street"`
    Suite   string `json:"suite"`
    City    string `json:"city"`
    Zipcode string `json:"zipcode"`
    Geo     struct {
      Lat string `json:"lat"`
      Lng string `json:"lng"`
    } `json:"geo"`
  } `json:"address"`
  Phone   string `json:"phone"`
  Website string `json:"website"`
  Company struct {
    Name        string `json:"name"`
    CatchPhrase string `json:"catchPhrase"`
    Bs          string `json:"bs"`
  } `json:"company"`
}
Enter fullscreen mode Exit fullscreen mode

Working from the top down, we'll first pull out Address

type Address struct {
  Street  string `json:"street"`
  Suite   string `json:"suite"`
  City    string `json:"city"`
  Zipcode string `json:"zipcode"`
  Geo     struct {
    Lat string `json:"lat"`
    Lng string `json:"lng"`
  } `json:"geo"`
}
Enter fullscreen mode Exit fullscreen mode

I'm half tempted to break out Geo, but in the end I think it can stay as part of the address struct. Now we'll move out Company.

type Company struct {
  Name        string `json:"name"`
  CatchPhrase string `json:"catchPhrase"`
  Bs          string `json:"bs"`
}
Enter fullscreen mode Exit fullscreen mode

And finally, the Users block using the newly created Address and Company structs.

type Users []struct {
  ID       int    `json:"id"`
  Name     string `json:"name"`
  Username string `json:"username"`
  Email    string `json:"email"`
  Address  Address `json:"address"`
  Phone    string `json:"phone"`
  Website  string `json:"website"`
  Company  Company `json:"company"`
}
Enter fullscreen mode Exit fullscreen mode

The nice thing about iterating on that same base code we started with is we only have to make slight changes. We'll alter our variable that had been r, to be called u which will hold our Users.

  var u Users
  json.Unmarshal(body, &u)
Enter fullscreen mode Exit fullscreen mode

OK, so we've got our users in memory now what can we do with them. Let's imagine they were on our mailing list and wanted an update each time a new post went up. We know from last time that sending an email through MailGun is very easy - in fact, we have a package we can just import. It's almost like I planned it that way! Before we get to that though...


Templating

Let's take a quick detour into Go templates. If you've done any web development you may be familiar with Handlebars or similar template systems there - this is more or less the same thing. We can take struct and use it to replace text within a "marked up" piece of text. Here is a basic example which you can see running on the Golang Playground. Let's go over what we're doing.

package main

import (
  "os"
  "text/template"
)

type Data struct {
  Name string
  City string
}

func main() {

  o := Data{"Steve", "Portland"}
  msgText := "It's {{.Name}} from {{.City}}!"

  t := template.Must(template.New("msg").Parse(msgText))

  err := t.Execute(os.Stdout, o)
  if err != nil {
    panic(err)
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, you can see we're declaring our template text, It's {{.Name}} from {{.City}}!. Then we wrap template.New().Parse() with template.Must(). .Must() will panic if .New() or .Parse() fails. t.Execute() does the actual work - in this case replacing the {{}} tokens and writing the result directly to standard out. You can see this in action over on the Golang Playground. Spoilers, it prints out It's Steve from Portland!.

We can use our Users and templates to very quickly do substitutions and end up with a nice personalized email.


Work That List

Back to our original code, we'll be adding the first part of the templating code just under our now updated Users variable and Unmarshal() call.

  msgText := "To: {{.Email}}\nHi {{.Username}}! There is a new post!\n\n\n"
  t := template.Must(template.New("msg").Parse(msgText))
Enter fullscreen mode Exit fullscreen mode

As you can see we are using a very similar setup to our templating example. Here we are replacing email and username with whatever is contained in the object passed in. But how do we use this with our Users? In this case, we're going to just use for/range to loop through.

  for _, v := range u {
    err := t.Execute(os.Stdout, v)
    if err != nil {
      panic(err)
    }
  }
Enter fullscreen mode Exit fullscreen mode

v will hold the value of one of our Users during each time through the for loop. We don't need the index in this case so we're using _ to tell Go we don't care about that variable. As with the previous example, we call t.Execute() and let it write directly to standard out. Finally, let's take a look at our updated code.

Full code listing

package main

import (
  "encoding/json"
  "io/ioutil"
  "net/http"
  "os"
  "text/template"
)

type Address struct {
  Street  string `json:"street"`
  Suite   string `json:"suite"`
  City    string `json:"city"`
  Zipcode string `json:"zipcode"`
  Geo     struct {
    Lat string `json:"lat"`
    Lng string `json:"lng"`
  } `json:"geo"`
}

type Company struct {
  Name        string `json:"name"`
  CatchPhrase string `json:"catchPhrase"`
  Bs          string `json:"bs"`
}

type Users []struct {
  ID       int     `json:"id"`
  Name     string  `json:"name"`
  Username string  `json:"username"`
  Email    string  `json:"email"`
  Address  Address `json:"address"`
  Phone    string  `json:"phone"`
  Website  string  `json:"website"`
  Company  Company `json:"company"`
}

func main() {
  APIURL := "https://jsonplaceholder.typicode.com/users"
  req, err := http.NewRequest(http.MethodGet, APIURL, nil)
  if err != nil {
    panic(err)
  }
  client := http.DefaultClient
  resp, err := client.Do(req)
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()
  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    panic(err)
  }

  var u Users
  json.Unmarshal(body, &u)

  msgText := "To: {{.Email}}\nHi {{.Username}}! There is a new post!\n\n\n"
  t := template.Must(template.New("msg").Parse(msgText))

  for _, v := range u {
    err := t.Execute(os.Stdout, v)
    if err != nil {
      panic(err)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Not too exciting by we did show how we can nest structs and introduced some basic templating. This post is getting a tad long so... Next time, we'll refactor it a little and add in some actual mail sending. After that, I'm not sure yet!


You can find the code for this and most of the other Attempting to Learn Go posts in the repo on GitHub.




💖 💪 🙅 🚩
shindakun
Steve Layton

Posted on December 16, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related