Progate Goコースを終えた人へ贈るWeb API開発入門

# はじめに

この記事は[Go 3 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/go3)の23日目の記事です。

## あなたは誰？

プログラミング歴1年半くらいで、普段はWebプログラマ(Go, React)のインターンとして働いています。

## なぜこのような記事を書くに至ったか

この記事は[Progate](https://prog-8.com)のGoコースを終えたけどこれからどうやって学んでいけばいいかわからない人のためのガイド的なものです。

筆者自身、完全プログラミング初心者のときProgateにお世話になったのですが、Goのすべてのコースを終えたあとどのように学んでいけばいいのかわからずに困惑した記憶があります。

Progateの言語紹介文句には「Googleが開発した 人気上昇中のサーバーサイド言語」とありますが、Progateのレッスンは基本的な文法を学ぶものであり、WebアプリやWeb APIを作成するガイドはありません。
巷で「やるといいよ！」と言われている[A Tour of Go](https://tour.golang.org)は難しくて手を出しにくいし、これも主に文法を学ぶものであり問題の解決にはなっていません。

Goを初心者に布教したいと思っている身として、無料かつ日本語で初心者がWeb APIの作り方を学べるガイドがないのはよくないと思ったのでこの記事を書くに至りました。

どんどん加筆修正していく予定ですので、有識者の方で追加してほしい章の提案などありましたらコメントや[Twitter](https://twitter.com/re_yuzuy)でお願いします！

注: わかりやすくするために厳密さに欠けるところが多々あるかと思いますがご了承ください。

## この記事の対象読者

題名にもある通りProgateのGoコースを終えてちょっとだけGoの文法勉強した人(プログラミング初心者)を対象としています。

具体的には

- Progate Go IVまでの内容を理解している + export,map,struct,methodを理解している
- CLIでの基本的なコマンドを理解している(ProgateのCommand lineコースを終えていれば大丈夫です)

ことを前提としています。

Progateを終えてすぐに来た、あるいはProgateをやっていない人は下記記事を読んで理解していただければ大丈夫だと思います。

- [【Go】基本文法①(基礎)](https://qiita.com/k-penguin-sato/items/1d0e1c6b4bf937996cd3)
- [【Go】基本文法②(フロー制御文)](https://qiita.com/k-penguin-sato/items/31b45fd3914797b654f0)
- [【Go】基本文法③(ポインタ・構造体)](https://qiita.com/k-penguin-sato/items/62dfe0f93f56e4bf9157)
- [【Go】基本文法④(配列・スライス)](https://qiita.com/k-penguin-sato/items/daad9986d6c42bdcde90)
- [【Go】基本文法⑤(連想配列・ Range)](https://qiita.com/k-penguin-sato/items/a320072fa09502bde3e9)

筆者はProgateのGoコースをやってからかなり時間が経っているため、Progate内での学習内容を把握しきれていません。
上記条件を満たしている方で理解できないところがある場合は、章の提案と同じくコメントや[Twitter](https://twitter.com/re_yuzuy)でご指摘いただければ幸いです！

## 筆者の環境

|  |  |
|:-:|:-:|
| OS | macOS Catalina 10.15.7 |
| Go | go1.15.4 darwin/amd64 |
| Homebrew | 2.6.1 |


# 目次

1. [Goの環境構築](#goの環境構築)
2. [Hello Web API](#hello-web-api)
3. [ToDo APIの開発](#todo-apiの開発)

# Goの環境構築

手元にMacしかないのでMacでの環境構築方法しか載せません。
(下手に間違った、あるいは古い情報を伝えてしまう恐れがあるので)
(有識者の方いましたらLinux/Windowsでの環境構築の編集リクエストやおすすめ記事の共有ぜひよろしくお願いします！)

## Goのダウンロード・インストール

色々な種類があるのですが、本記事では手軽さを重視して[Homebrew](https://brew.sh/index_ja)を使用します。

Homebrew自体のインストールに関しては[公式サイト](https://brew.sh/index_ja)を参照してください。

たった一行のコマンドで完了します。

```
brew install go
```

諸々の処理が終わったら`go version`というコマンドを実行してみてください。

```
go version go1.15.4 darwin/amd64
```

のように表示されたら完了です。

## GOPATH・環境変数の設定

なぜ必要なのかなどの説明は一旦省きますが、気になる方は[GOPATH に(可能な限り)依存しない Go 開発環境(Go 1.15 版)](https://zenn.dev/tennashi/articles/3b87a8d924bc9c43573e)という記事を読むとよいかと思います。

必要なディレクトリを作成します。下記コマンドを実行するだけでOKです。

```bash
mkdir -p $HOME/go/src $HOME/go/pkg $HOME/go/bin
```

shell起動時にGoに必要な環境変数を設定するようにします。
`echo $SHELL`というコマンドを実行して、末尾が`bash`であれば`$HOME/.bash_profile`に、`zsh`であれば`$HOME/.zshrc`に下記テキストをコピペしてください。

ファイルがなければ作成してください。

```bash
export GOPATH="$HOME/go"
export GOBIN="$GOPATH/bin"
export GO111MODULE=auto
```

## Hello World in your local environment

環境構築が完了したので早速動作確認しましょう！

まず適当にディレクトリを作ります。

```
mkdir $HOME/Desktop/first-go
```

そのディレクトリに移動したらGo Moduleを初期化します。
Go Moduleについても今は作法だと思って先に進んでもらって問題ないです。

Goのプロジェクトのパッケージやそのバージョン管理に使われるものです。

```
go mod init [module]
```

[module]にはそのプロジェクトのリポジトリのURLを設定するのが慣例なので、GitHubのアカウントがある人は`go mod init github.com/[username]/first-go`とするとよいでしょう。

ない場合は`go mod init first-go`でも問題ありません！

今後出てくる[module]は自分の環境のものを参照してください。

go.modというファイルが作成されていれば成功です。

そして次にmain.goというファイルを作成して、下記コードをコピペしましょう。

```go:main.go
package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}
```

コピペできたら`go run main.go`というコマンドでコードを実行してみましょう。
`go run`はmainパッケージmain関数があるファイルを指定するとコンパイル・実行を行ってくれるコマンドです。

```
Hello World!
```

と表示されれば環境構築は完了です！

# Hello Web API

まずはディレクトリの作成、Go Moduleの初期化から行っていきます。

```
mkdir $HOME/Desktop/first-web-api
cd $HOME/Desktop/first-web-api
go mod init [module]
```

## 外部パッケージのダウンロード

初期化ができたら今回使う外部パッケージのダウンロードを行います。
標準パッケージでも実装できるはできるのですが、本記事ではついでに外部パッケージの使い方を学んでしまおう、ということでGinというフレームワークを使います。
さらに、タスクのIDに`UUID`というものを使うのでそれを生成するためのパッケージもダウンロードします。

下記コマンドを実行しましょう。

```
go get github.com/gin-gonic/gin
go get github.com/google/uui
```

Goでの外部パッケージのダウンロードは`go get`コマンドによって行います。

go.sumというファイルが生成されて、go.modが下記のように書き換わっていたら成功です。

```:go.mod
module [module]

go 1.15

require (
	github.com/gin-gonic/gin v1.6.3 // indirect
	github.com/google/uuid v1.1.2 // indirect
)
```

## とりあえず何も考えずにWeb APIでHello Worldしてみる

脳死で写経→動かす→解説という流れで書いていきます。
main.goを作成して以下のコードをコピペしてください。

```go:main.go
package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()

    r.GET("/hello", func(c *gin.Context) {
        c.String(200, "Hello World!")
    })

    r.Run()
}
```

`go run main.go`で実行したら、任意のブラウザで`localhost:8080/hello`にアクセスしてみてください。

`Hello World!`と表示されていたら成功です！

確認できたら`Ctrl+C`を押して一旦プログラムを停止させましょう。

## 解説

最初の

```go
r := gin.Default()
```

ではRouterの初期化を行っています。
Routerはどこにアクセスしたらどういう処理を行うか(ハンドラー)を登録するものと思ってもらえれば大丈夫です。

次の

```go
r.GET("/hello", func(c *gin.Context) {
    c.String(200, "Hello World!")
})
```

ではRouterに対してハンドラーの登録を行っています。
`"/hello"`に`GET`メソッドでリクエストが来たら、ステータスコード`200`で`"Hello World"`という文字列を返すという処理を記述しています。

一つ目の引数には`"/hello"`のような引数を、二つ目の引数には`func(c *gin.Context)`のような引数が`(c *gin.Context)`な関数を入れます。

急によくわからない単語が複数出てきて、読者の皆さんの頭の中には少年マガジンよろしく「！？」が浮かんでいると思うので、それらについて解説していきます。

### HTTP

GETメソッドやステータスコードなど、これらはHTTPの用語です。

HTTPとは通信プロトコルの一種で[MDN Web Docs](https://developer.mozilla.org/ja/docs/Web/HTTP)の小難しい説明によると

> Hypertext Transfer Protocol (HTTP) は HTML などのハイパーメディア文書を転送するためのアプリケーション層プロトコルです。このプロトコルはウェブブラウザー(クライアント)とウェブサーバー間の通信を目的として設計されていますが、他の用途でも使用されることがあります。 HTTP は旧来のクライアント・サーバーモデルに則っており、クライアントはサーバーにリクエストを送信するためにポートを開き、サーバー側からのレスポンスが返ってくるまで待機します。 HTTP はいわゆるステートレスプロトコルであり、つまりサーバーは二つのリクエスト間で何もデータを保持しません。 HTTP は多くの場合 TCP/IP 層上の通信で使用されますが、任意の信頼性があるトランスポート層、すなわち、 UDP のように知らぬ間にメッセージが失われるようなことがないプロトコルでも使用されることがあります。 RUDP — UDP に信頼性を追加したもの — も代替用として適合します。

ということですが、とりあえずは「ブラウザとWebサーバーでどういう風に通信するか偉い人たち(IETF)が決めたもの」という認識で大丈夫です。

この世に存在しているWebサーバーの多くはHTTPサーバーであり、本記事で作成するWeb APIもHTTPサーバーです。
なのでHTTPの用語が出てきたわけです。

#### メソッド

それで結局`GET`メソッドというのはなんなんだという話ですが、HTTPメソッドの一種です。
HTTPメソッドとは、HTTPサーバーにリクエストを送信するときにそのリクエストがどういう種類なのかを伝えるためのものです。

HTTPメソッドは

- `GET`
- `HEAD`
- `POST`
- `PUT`
- `DELETE`
- `CONNECT`
- `OPTIONS`
- `TRACE`
- `PATCH`

の全9種類しかありません。

`GET`は最も基本的なメソッドであり、リソースを取得したいときに使われるメソッドです。
例えばこのQiitaの記事にアクセスするとき、ブラウザは`https://qiita.com/yuzuy/items/cf018bd8adaed1f55c84`に対して`GET`メソッドでリクエストを送っています。
ブラウザで直にアドレスを入力してアクセスする際は`GET`メソッドが使われており、`localhost:8080/hello`にも`GET`メソッドでリクエストを送ったので`Hello World!`という文字列が表示されました。

#### ステータスコード

ステータスコードはメソッドとは逆にHTTPサーバーがHTTPクライアント(ブラウザ)に状態を伝えるためのものです。

先程のプログラムでは`200`を返していましたが、`200`はOK、つまりリクエストの成功を表すコードです。
ステータスコードは結構色々ありますが、200台は正常系、400台はクライアントが悪い系、500台はサーバーが悪い系という風にざっと覚えておけば大丈夫でしょう。

[MDN Web Docs](https://developer.mozilla.org/ja/docs/Web/HTTP/Status)が一番正確かと思いますが、[HTTP Cats](https://http.cat/)や[HTTP Status Dogs](https://httpstatusdogs.com/)などの動物の画像で説明しているサイトを見てみてもわかりやすいかもしれません。

`404 Not Found`とかは有名ですね。

ざっとHTTPの解説をしたところで、コードの解説に戻ろうと思います。

と言っても

```go
r.Run()
```

でサーバーが起動するというだけですが。

# ToDo APIの開発

Web APIでのHello Worldも終えたところで、今度は少し応用的なAPIを開発していきます。

この章では以下の機能を備えたToDoアプリ用のAPIを開発します。

- タスク一覧取得
- タスク追加
- タスク編集
- タスクの削除

## タスク追加

タスクの追加ができずにタスク一覧取得APIを開発しても無が帰ってくるだけなので、まず最初はタスクの追加を実装します。

先程のプロジェクトを流用してもいいですし、分けたい方は新しく作ってもいいです。

まずはタスクの構造を決めます。
`main.go`があるディレクトリの下に新しく`todo`というディレクトリを作って、その下に`task.go`というファイルを作成しましょう。

今回タスクは

- ID
- 名前
- 完了済みか
- 作成日時

の要素を持つことにします。

`todo.go`に`Task`を定義しましょう。

```go:todo/task.go
import "time"

type Task struct {
    ID        string
    Name      string
    IsDone    bool
    CreatedAt time.Time
}
```

`time.Time`は標準パッケージ`time`に定義されている時間を表す型です。

`Task`の定義が終わりましたが、それを保存しておく場所がありません。
なので次は`DB`を定義しましょう。
初期化関数も一緒に定義します。

`db`というディレクトリを作り、`db.go`というファイルを作成しましょう。
今回は`id`をキーにした連想配列で管理します。

`todo`パッケージをインポートするには`[module]/todo`を指定します。

```go:db/db.go
package db

import "[module]/todo"

type DB struct {
    m map[string]*todo.Task
}

func New() *DB {
    return &DB{
        m: make(map[string]*todo.Task),
    }
}
```

タスクを追加するメソッドを実装しましょう。

```go:db/db.go
package db

import (
    "errors"

    "[module]/todo"
)

type DB struct {
    m map[string]*todo.Task
}

func New() *DB {
    return &DB{
        m: make(map[string]*todo.Task),
    }
}

func (db *DB) func AddTask(task *todo.Task) error {
    // nilチェック
    if task == nil {
        return errors.New("this task is nil")
    }

    // 同じIDのタスクは追加できないようにする
    _, ok := db.m[task.ID]
    if ok {
        return errors.New("this task already added")
    }

    db.m[task.ID] = task

    return nil
}
```

Goでは関数の戻り値で`error`を返してエラーハンドリングを行います。
今回は`task`が`nil`だったとき、`ID`が同じタスクを追加しようとしたときにエラーを返すようにしました。
タスクのIDで使用する予定の`UUID`は[UUID(v4) がぶつかる可能性を考えなくていい理由](https://qiita.com/ta_ta_ta_miya/items/1f8f71db3c1bf2dfb7ea)によると被る期待値は230京回らしいですが、この前Twitterで被った人がいたので(誰のTweetかは忘れちゃいましたが)、ここはマーフィーの法則に従っていくということで。

呼び出すときは

```go
err := db.AddTask(nil)
if err != nil {
    log.Println(err)
    return
}
```

のように処理します。

基本的に失敗する可能性のある全ての関数は`error`を返すようにしましょう。

今回は`Server`という構造体に`*gin.Engine`(Router)と`*db.DB`を持たせて、`Server`のメソッドでハンドラーを実装、`Server.Start()`でRouterにハンドラーの登録し、起動を行うようにします。

これは言葉で説明するよりもコードを見た方がわかりやすいと思うので、あまり理解できなくても少し先に進むことをおすすめします。

まず`server`というディレクトリとその下に`server.go`というファイルを作成してください。
`Server`の定義と初期化関数の実装をします。

```go:server/server.go
package server

import (
    "[module]/db"

    "github.com/gin-gonic/gin"
)

type Server struct {
    r  *gin.Engine
    db *db.DB
}

func New() *Server {
    return &Server{
        r:  gin.Default(),
        db: db.New(),
    }
}
```

Ginのハンドラーは`(*gin.Context)`が引数の関数、メソッドであればよいので`Server.AddTasks(c *gin.Context)`というメソッドを定義して、タスクを追加するエンドポイントに対するハンドラーとします。


タスク追加のリクエストは`POST`で受け取るようにします。
`POST`はリソースの作成を行うメソッドです。
`POST`ではリクエストボディというところデータを入れることができるので、そこに`application/x-www-form-urlencoded`という形式で新しいタスクの情報を入れてもらって、それを参照してタスクを追加していきます。

```go:server/server.go
import (
    // 既存のimportに追加してください
    "time"

    "github.com/google/uuid"
)

func (s *Server) addTask(c *gin.Context) {
    // application/x-www-form-urlencodedでリクエストを受け取る場合はc.PostFormというメソッドで値を取り出せます
    name := c.PostForm("name")
    // nameが空だった場合
    if name == "" {
        name = "untitled"
    }
    task := &todo.Task{
        ID:        uuid.New().String(), // uuidの生成
        Name:      name,
        CreatedAt: time.Now(), // 現在時刻取得
    }

    err := s.db.AddTask(task)
    if err != nil {
        // err.Error()でエラーメッセージが取り出せます
        // 本記事ではエラーはすべてステータスコード500を返します
        c.String(500, err.Error())
        return
    }

    // 201はCreated、作成されたことを表すステータスコードです
    c.String(201, "Created")
}
```

ハンドラーの実装が終わったので、ハンドラーの登録、サーバーの起動を行う`Server.Start()`を実装していきます。

```go:server/server.go
func (s *Server) Start() error {
    s.r.POST("/tasks", s.addTask)

    // 先程はハンドリングしていませんでしたが、実はRunメソッドもエラーを返しています
    return s.r.Run()
}
```

あともう少しです。
`main`で`Server`の初期化、起動をするだけです。

```go:main.go
pakcage main

import (
    "log"

    "[module]/server"
)

func main() {
    s := server.New()
    err := s.Start()
    if err != nil {
        log.Println(err)
    }
}
```

これで`go run main.go`を実行すれば立ち上がるはずです！
少し長かったかと思いますが、他のAPIを実装するためのベースを築くことができたので次からの機能追加は楽になりました！

ブラウザでPOSTリクエストを送るのは少し面倒なので、動作テストは`cURL`というコマンドを使って行います。

以下コマンドで以下のような文字列が表示されていたら成功です。

```:command
curl -X POST -d 'name=hoge' -v http://localhost:8080/tasks
```

`-X`はメソッドを指定する、`-d`はリクエストボディを入力する、`-v`はより詳しくやり取りを表示するためのオプションです。

```:result
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /tasks HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 5
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 5 out of 5 bytes
< HTTP/1.1 201 Created
< Content-Type: text/plain; charset=utf-8
< Date: Wed, 23 Dec 2020 13:44:40 GMT
< Content-Length: 7
< 
* Connection #0 to host localhost left intact
Created* Closing connection 0
```

よく読むと`POST`で`/tasks`にリクエストしていることや、サーバーが`201`を返していることがわかると思います。

### チャレンジ

今のままではタスク一覧が見れないため、プログラムが本当に正常に作動しているのかわかりません。
コードのどこかでログを出力して正常に作動しているのか確かめてみましょう。

## タスク一覧取得

現状ではログでしかタスク一覧を見れないので、フロントエンド(Webアプリやモバイルアプリ)で見られるようにタスク一覧を取得するためのAPIを実装しましょう！

まず`db.DB`に取得するための新しいメソッドを実装します。

```go:db/db.go
func (db *DB) FindTasks() []*todo.Task {
    // tasksに入る要素の数はlen(db.m)だとわかっているので最初にその分のcapを確保しておくことで少し処理が早くなります
    tasks := make([]*todo.Task, 0, len(db.m))

    for _, v := range db.m {
        tasks = append(tasks, v)
    }

    return tasks
}
```

あとは`Server`にハンドラー用のメソッドを実装して、`Server.Start`で登録を追加するだけで完了です！

```go:server/server.go
func (s *Server) findTasks(c *gin.Context) {
    tasks := s.db.FindTasks()

    // 先程まではString(text)で値を返していましたがフロントエンド的に扱いやすいJSONというフォーマットで返します
    // 同じAPIでデフォルトのフォーマットが違うのはよろしくないのでaddTaskも本当はJSONで返したほうがよいですね
    c.JSON(200, tasks)
}
```

```go:server/server.go
func (s *Server) Start() error {
    s.r.GET("/tasks", s.findTasks)
    s.r.POST("/tasks", s.addTask)

    return s.r.Run()
}
```

これでサーバーを再起動すればタスク一覧APIが利用できるはずです！

`cURL`を使って確認してみましょう！
これは`GET`でリクエストを受け取るようになっているので、ブラウザでも確認できます。

`cURL`で確認する場合はデフォルトで`GET`を使うようになっているので`-X`オプションは不要です。

```:command
curl -v http://localhost:8080/tasks
```

### チャレンジ

これだとレスポンスの`JSON`のフィールドが`IsDone`や`CreatedAt`のようにキャメルケースになってしまっています。
例↓

```json
[
  {
    "ID": "43176919-b8d6-4811-a78c-67f94a79be4e",
    "Name": "untitiled",
    "IsDone": false,
    "CreatedAt": "2020-12-23T23:27:38.239276+09:00"
  }
]
```

`JSON`のフィールドは全て小文字のスネークケースにするのが慣例なので(今まで見てきた殆どのAPIのレスポンスではスネークケースでした)下記のようなレスポンスを返せるようにしてみましょう。

```json
[
  {
    "id": "43176919-b8d6-4811-a78c-67f94a79be4e",
    "name": "untitiled",
    "is_done": false,
    "created_at": "2020-12-23T23:27:38.239276+09:00"
  }
]
```

`Task`構造体のフィールドに`json`タグを付与することで可能です。
下記記事を参考にしてやってみましょう！
[Goの構造体にメタ情報を付与するタグの基本](https://qiita.com/itkr/items/9b4e8d8c6d574137443c)

## タスク削除

今のままだと間違ったタスクを追加してしまったときどうしようもできません。
このままだと不要なタスクが永遠に溜まり続けてしまいます。

タスク削除APIを実装してタスクを削除できるようにしましょう！

一覧取得と同様、まずは`db.DB`にメソッドを追加します。

```go:db/db.go
func (db *DB) RemoveTask(id string) error {
    // 存在しないタスクを削除しようとしたらエラーを返す
    _, ok := db.m[id]
    if !ok {
        return errors.New("this task not found")
    }

    // db.mからidがキーの要素を削除する
    delete(db.m, id)

    return nil
}
```

次も同様、`Server`にメソッドを追加して`Server.Start`を更新します。

```go:server/server.go
func (s *Server) Start() error {
    s.r.GET("/tasks", s.findTasks)
    s.r.POST("/tasks", s.addTask)
    // DELETEはリソースを削除するときに使われるメソッドです
    // エンドポイントの頭を":"にすることでその値をクライアントが決められるパラメーターになり、ハンドラーで参照できるようになります
    s.r.DELETE("/tasks/:id", s.removeTask)

    return s.r.Run()
}
```

```go:server/server.go
func (s *Server) removeTask(c *gin.Context) {
    // idパラメーターの取得
    // /tasks/hoge にリクエストが来た場合はidに"hoge"が入る
    id := c.Param("id")
    err := s.db.RemoveTask(id)
    if err != nil {
        // gin.Hはmap[string]interface{}と同じです
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, gin.H{})
}
```

再度サーバーを再起動し、任意のタスクを削除してみましょう！

## タスク編集

この機能は今まで学習してきたことを活かして自分で実装してみましょう！

以下の仕様に従って実装しましょう。

- タスク削除APIと同じようにURLでパラメーターを受け取る
- `PATCH`メソッドでリクエストを受け取る
- `application/x-www-form-urlencoded`でリクエストボディを受け取る
- `name`と`is_done`の情報が更新できるようにする
- `name`が空の場合はエラーを返す

実装例は[yuzuy/go-guide-after-progate](https://github.com/yuzuy/go-guide-after-progate)にあるので、自分の実装と比較したい場合やどうしてもわからなかった場合は参照してください。

# まとめ

本記事ではGoの環境構築からWeb APIでのHello World、簡単なWeb APIの実装まで行ってみました。
本記事を通して少しでも読者の方が成長できたら幸いです。

今後はRDBMSを使ったToDo APIの拡張の解説などを追加しようと考えています。
はじめにでも書きましたが、なにか気になるところや提案がありましたらぜひコメントや[DM](https://twitter.com/re_yuzuy)お願いします！

それでは良いお年を！

## 参考記事・引用元

- [Progate](https://prog-8.com)
- [A Tour of Go](https://tour.golang.org)
- [Homebrew](https://brew.sh/index_ja)
- [MDN Web Docs](https://developer.mozilla.org/ja/)
- [HTTP Cats](https://http.cat/)
- [HTTP Status Dogs](https://httpstatusdogs.com/)
- [【Go】基本文法①(基礎)](https://qiita.com/k-penguin-sato/items/1d0e1c6b4bf937996cd3)
- [【Go】基本文法②(フロー制御文)](https://qiita.com/k-penguin-sato/items/31b45fd3914797b654f0)
- [【Go】基本文法③(ポインタ・構造体)](https://qiita.com/k-penguin-sato/items/62dfe0f93f56e4bf9157)
- [【Go】基本文法④(配列・スライス)](https://qiita.com/k-penguin-sato/items/daad9986d6c42bdcde90)
- [【Go】基本文法⑤(連想配列・ Range)](https://qiita.com/k-penguin-sato/items/a320072fa09502bde3e9)
- [GOPATH に(可能な限り)依存しない Go 開発環境(Go 1.15 版)](https://zenn.dev/tennashi/articles/3b87a8d924bc9c43573e)
- [UUID(v4) がぶつかる可能性を考えなくていい理由](https://qiita.com/ta_ta_ta_miya/items/1f8f71db3c1bf2dfb7ea)
- [Goの構造体にメタ情報を付与するタグの基本](https://qiita.com/itkr/items/9b4e8d8c6d574137443c)