Interacting with the Dev.to Article API
Tags: go, atlg, continuingeducation, beginners
Published: 2019-03-09 19:19:41

---
title: Interacting with the Dev.to Article API
published: true
description: "Let's take a journey into pulling articles from the Dev.to article API."
tags: go, atlg, continuing-education, beginner
cover_image: https://dev.shindakun.net/img/attemptinggo_w.png
series: "ATLG Sidebar"
---

# ATLG Sidebar

## Uhh OK

Hay all! If this is your first time, welcome! If not welcome back! This week I'm starting a new "series" here on Dev.to. This week so I decided to try to flex my muscle memory and write a small utility that would interact with an API. I'm using the `dev.to/api/articles` endpoint in this case. This dovetails into another side project I want to start poking around with! I need a chunk of data on hand for that (we'll come back to that on a different sidebar post if it ever comes together).

***

## Code Walkthrough

```go
package main

import (
  "encoding/json"
  "fmt"
  "io/ioutil"
  "net/http"
  "sync"
  "time"
)
```

If you write Go, [JSON-to-Go](https://mholt.github.io/json-to-go/) is going to be your friend. If we need to unmarshal JSON we can simply paste in a sample of the JSON and it will give you a struct. Nice. We don't actually need the entire `struct` we could have left only the `ID int32 'json:"id"'` as its the only one we use. I'm included the entire thing now, we may use it in the future.

```go
// Articles array JSON struct
type Articles []struct {
  TypeOf                 string    `json:"type_of"`
  ID                     int32     `json:"id"`
  Title                  string    `json:"title"`
  Description            string    `json:"description"`
  CoverImage             string    `json:"cover_image"`
  PublishedAt            time.Time `json:"published_at"`
  TagList                []string  `json:"tag_list"`
  Slug                   string    `json:"slug"`
  Path                   string    `json:"path"`
  URL                    string    `json:"url"`
  CanonicalURL           string    `json:"canonical_url"`
  CommentsCount          int       `json:"comments_count"`
  PositiveReactionsCount int       `json:"positive_reactions_count"`
  User                   struct {
    Name            string      `json:"name"`
    Username        string      `json:"username"`
    TwitterUsername string      `json:"twitter_username"`
    GithubUsername  interface{} `json:"github_username"`
    WebsiteURL      string      `json:"website_url"`
    ProfileImage    string      `json:"profile_image"`
    ProfileImage90  string      `json:"profile_image_90"`
  } `json:"user"`
}
```

I started to lay this out as if we were going to use it as an importable package. It could have been done mostly inline in `main()` but maybe we'll flesh this out into a full DEV API client. I'll have to take a look at the v0 API a bit closer and see what is actually supported.

```go
// DevtoClient struct
type DevtoClient struct {
  DevtoAPIURL string
  Client      *http.Client
}

// New returns our DevtoClient
func New(apiurl string, client *http.Client) *DevtoClient {
  if client == nil {
    client = http.DefaultClient
  }
  return &DevtoClient{
    apiurl,
    client,
  }
}
```

Formatting our requests this way might be overkill but, for a start, it puts us in a good spot. The requests can take a few different parameters but we aren't concerned with any of those. At least not this time around, we only want the articles themselves.

```go
// FormatPagedRequest returns *http.Request ready to do() to get one page
func (dtc DevtoClient) FormatPagedRequest(param, paramValue string) (r *http.Request, err error) {
  URL := dtc.DevtoAPIURL + "articles/?" + param + "=" + paramValue
  fmt.Printf("%v\n", URL)
  r, err = http.NewRequest(http.MethodGet, URL, nil)
  if err != nil {
    return nil, err
  }
  return r, nil
}

// FormatArticleRequest returns http.Request ready to do() and get an article
func (dtc DevtoClient) FormatArticleRequest(i int32) (r *http.Request, err error) {
  URL := fmt.Sprintf(dtc.DevtoAPIURL+"articles/%d", i)
  r, err = http.NewRequest(http.MethodGet, URL, nil)
  if err != nil {
    return nil, err
  }
  return r, nil
}
```

This time around I am experimenting with `sync.waitGroup`. [WaitGroups](https://golang.org/pkg/sync/#WaitGroup) allow us to kick off a series of Goroutines and wait for them to finish before moving on with the code. We'll see further on in the code when `getArticle()` executes as a Goroutine. It is what actually gets the article from the API and writes it to disk. This way we're grabbing one set of 30 article ids. As we parse those we begin getting the articles. Once we've received them all only then do we move on to the next set.

```go
func getArticle(dtc *DevtoClient, i int32, wg *sync.WaitGroup) {
  defer wg.Done()
  r, err := dtc.FormatArticleRequest(i)
  if err != nil {
    panic(err)
  }

  resp, err := dtc.Client.Do(r)
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()

  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    panic(err)
  }
  fileName := fmt.Sprintf("%d.json", i)
  ioutil.WriteFile("./out/"+fileName, body, 0666)
}
```

`main()` is straightforward enough. We create our client, using `http.DefaultClient`. We've provided the ability to use an alternate configuration if we need it in the future. `doit` and `c` will be controlling our `for` loop and the main body of the program.

```go
func main() {
  dtc := New("https://dev.to/api/", nil)
  doit := true
  c := 1
```

In each run, through the loop, we get a single page of articles. We then set up our `WaitGroup` and our `articles` variable. Once we have unmarshalled the articles JSON we get the length of that array.  That length tells `WaitGroup` how many "times" to wait. Note that we are calling `defer wg.Done()` as the first line in the `getArticle()`. This subtracts one from the `WaitGroup` total allowing us to move on when finished. The current Dev.to article API returns an empty array, `[]` when there is no data for a page. We check to see if we have that as a response and stop if so.

```go
  for doit {
    req, err := dtc.FormatPagedRequest("page", fmt.Sprintf("%d", c))
    if err != nil {
      panic(err)
    }
    resp, err := dtc.Client.Do(req)
    if err != nil {
      panic(err)
    }
    defer resp.Body.Close()

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

    var wg sync.WaitGroup
    var articles Articles

    json.Unmarshal(body, &articles)
    wg.Add(len(articles))

    for i := range articles {
      go getArticle(dtc, articles[i].ID, &wg)
    }
    wg.Wait()

    if string(body) != "[]" {
      c++
      continue
    }
    doit = false
  }
}
```

***

## Wrapping Up

There we go, the first "sidebar"! I'll probably be adding the code for this into the main "Attempting To Learn Go" repo over on GitHub. Now that I think about it I still need to post last weeks over there!

Have you done any API work in Go? Anything you would do a bit differently? Let me know in the comments below. Consructive comments are always welcome!

***

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) |

***