Attempting to Learn Go - Now Sending REST Requests
Tags: go, continuingeducation, atlg
Published: 2018-12-08 20:35:34

---
title: Attempting to Learn Go - Now Sending REST Requests
published: true
description: "Forget GET, let's POST! Continuing adventures with Go and APIs."
tags: go, golang, continuing-education, atlg
cover_image: https://shindakun.glitch.me/content/images/2018/01/attemptinggo.png
series: "Attempting to Learn Go"
---

# Forget GET, let's POST

So far, we've gone through a couple small examples on how to request data from a remote REST API. This time, however, we're going to explore how to submit data to a remote endpoint. You could probably use something like [`go-swagger`](goswagger.io) as mentioned by [@bgadrian](https://dev.to/bgadrian/comment/73o2) to do all this and probably would in a production setting. For learning purposes though, we're going to just tackle what we can by using the standard library or small packages I've written that use the standard library.

This time we're going to look at sending an email using the MailGun API. I choose MailGun because is relatively straightforward and [well documented](https://documentation.mailgun.com/en/latest/quickstart-sending.html#send-via-api) - and I happen to have an account already. If you are going to follow along, you [should sign-up](https://signup.mailgun.com/new/signup).

***

## GOPATH

But first! I was hoping to not really ever getting into GOPATH since it can be a little weird depending on the OS you are running. With that in mind, I'll the [wiki](https://github.com/golang/go/wiki/SettingGOPATH) cover most of the details and just touch on it as little as possible. For the most part, I'm assuming if you are reading this you likely already have Go installed and ready to go. I tried to put this project together a little differently as I wanted to start putting together some reusable packages we can hopefully draw on in future posts. With this in mind, we're going to write a very simple "send-only" MailGun client. My first step was to create a directory in my GOPATH (which for me is `/Users/steve/Code/go`) in my `github.com/shindakun` directory. Maybe I should have put it directly in the `ATLG` repo since it will end up there anyway... Or convert it to a Go Module... I'll worry about that later I suppose, let's make those directories.

```bash
mkdir $GOTPATH/github.com/shindakun/mailgunner
mkdir $GOTPATH/github.com/shindakun/mailgunner/client
```

I'm going to cover the code for the "client" first. Remember that we're attempting to do as much as we can with just the standard library - this is why we're not importing an existing MailGun client for Go. In our client directory create a `main.go` file and start setting up our package.

*Quick aside - I recommend using [VSCode](https://code.visualstudio.com/) and the `ms-vscode.go` plugin ... or not - use whatever editor your comfortable with.*

```go
package client

import (
  "net/http"
  "net/url"
  "strings"
)
```

There are not going to be too many moving parts to the client so we'll only need a few imports. `strings` and `net/url` will help get our data ready to add to the request and `net/http` will take of actually putting the HTTP request together.

Imports out of the way, there are a couple ways I could see doing the next part. But, I think the one presented here makes the most sense - in my mind at least. First, we declare a struct for `MgClient`, this struct will hold the API URL, our API token and [`http.Client`](https://golang.org/pkg/net/http/#Client).

`NewMgClient()` will take in the API URL and Token and return our struct. This setup allows us to use the package in multiple projects with different MailGun accounts, we can just pass it around as needed.

```go
type MgClient struct {
  MgAPIURL string
  MgAPIKey string
  Client   *http.Client
}

func NewMgClient(apiurl, apikey string) MgClient {
  return MgClient{
    apiurl,
    apikey,
    http.DefaultClient,
  }
}
```

The real heavy lifting of the client package comes next with `FormatEmailRequest()`. It is a method of `MgClient` so it's passed back to the caller with the return from `NewMgClient()` - we'll look at that later. There are a few different things happening here so I've split the function in half to cover each (the full code listing will either be below or over on GitHub). The `data` object uses `url.Values{}` allowing us to put together our key-value pairs for what is ultimately a form submission. We're passing in all our variable values when we call the function.

```go
func (mgc *MgClient) FormatEmailRequest(from, to, subject, body string) (r *http.Request, err error) {
  data := url.Values{}
  data.Add("from", from)
  data.Add("to", to)
  data.Add("subject", subject)
  data.Add("text", body)
```

With our email details out of the way, we need to build our HTTP request. This is similar to what we had been doing previously. Notice, however, that we are using `http.MethodPost` and not `http.MethodGet`. Additionally, we are using `strings.NewReader()` to create an `io.Reader` into which we pass `data.Encode()`. `.Encode()` simply [URL encodes](https://golang.org/pkg/net/url/#Values.Encode) our key-value pairs so we'll get something like `to=emailaddress&from=someotheraddress` and so on. We then set the [Basic Authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) and the [Content-Type header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type). The format in this case is not JSON but a URLencoded form.

```go
  r, err = http.NewRequest(http.MethodPost, mgc.MgAPIURL+"/messages", strings.NewReader(data.Encode()))
  if err != nil {
    return nil, err
  }
  r.SetBasicAuth("api", mgc.MgAPIKey)
  r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
  return r, nil
}
```

And that's about it for "client" package! Simple right? I'll try to circle back around to this code in some future post where we add a test or two. You can see I've already begun to think about that by making sure we return from `FormatEmailRequest()` with an error if something goes wrong. The thinking here is that we want to error in the package but pass it back out so whoever is using the package can handle it however they choose.

***

## Mailgunner

Now we'll go back up to our main `mailgunner` directory and create a new `main.go` file. Our imports are pretty clean, consisting of only `fmt` and `io/ioutil`, from the standard library, plus our new `client` package and one other. I made a brief allusion to the [`envy`](https://github.com/shindakun/envy) package in my [Slackbot post](https://dev.to/shindakun/a-simple-slack-bot-in-go---the-bot-4olg) it is a very small module which simply gets an environment variable or returns an error. Using ENV variables is a nice way to [keep API keys out of Git repos](https://12factor.net/config). The code for "ENVy" could easily be included (it's only 5 lines after all) but I think this keeps everything a bit cleaner. Rather the write it over an over it becomes its own package. Below you can see `envy.Get()` being used to get the MailGun API key from the `MGKEY` environment variable.

```go
package main

import (
  "fmt"
  "io/ioutil"

  "github.com/shindakun/envy"
  "github.com/shindakun/mailgunner/client"
)

func main() {
  mgKey, err := envy.Get("MGKEY")
  if err != nil {
    panic(err)
  }
```

With the basic setup out of the way, we're going to get to the good stuff. First, we'll create a new `MgClient{}` and store it in `mgc`. We then call `mgc.FormatEmailRequest()` with our test message. You could also move the API URL up out of the code into an ENV variable or a `const` as well if you want.

```go
  mgc := client.NewMgClient("https://api.mailgun.net/v3/youremaildomain.com",
    mgKey)

  req, err := mgc.FormatEmailRequest("<Name> some@email.domain",
    "other@email.domain", "Test email", "This is a test email!")
  if err != nil {
    panic(err)
  }
  ```

From here on out the code will resemble our previous examples for the most part. We make the actual request with `mgc.Client.Do()`, read the `[]byte` data out of `res.Body`, and finally convert to a string and print.

```go
  res, err := mgc.Client.Do(req)
  if err != nil {
    panic(err)
  }
  defer res.Body.Close()

  body, err := ioutil.ReadAll(res.Body)
  if err != nil {
    panic(err)
  }

  fmt.Println(string(body))
}
```

And when all is said and done our output should look something like this.

```bash
$ MGKEY=key-key go run main.go
{
  "id": "<20181206152053.2.AEFB08ACDA47726E@youremaildomain.com>",
  "message": "Queued. Thank you."
}
```

![Our email as it appears in Gmail](https://thepracticaldev.s3.amazonaws.com/i/lv2w3pry0jl2u3uloqqd.png)

## Next time

We're getting closer to doing something with the parts we've been laying out - not quite there but close. Next, we'll be modifying our API "getter" [code from a couple posts back](https://dev.to/shindakun/attempting-to-learn-go---continuing-rest-adventures-2l4l) to pull down a list of a list of users from a remote endpoint. Once we have the users it should be a short trip to get the code in place to send everyone an email.

***

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

{% github shindakun/atlg %}

---

| Enjoy this post? |
|:----:|
| [How about buying me a coffee?](https://ko-fi.com/shindakun) |

---