Go初心者のためのTODO APIハンズオン 前編

# はじめに
弊社ではバックエンドにGo言語を採用しています。
ここ最近自チームにGo言語未経験で入社したメンバーがいたのでそんなメンバーに向けてGoの基本的な文法を学んだ次のステップとして簡単なAPIを作成するハンズオンの教材を書きました。
対象読者としては[A Tour of Go](https://go-tour-jp.appspot.com/)などをやり基本的な文法は知っているが、動くものは一度も作ったことがないという人を対象にしています。

### 使用技術
- Go 1.21.5
  - echo (https://github.com/labstack/echo)
  - sqlx (https://github.com/jmoiron/sqlx)
- MySQL 5.7
- Docker v24.0.7
- Docker Compose v2.23.3

### ソースコード
- https://github.com/aaaasahi/go-todo-app

### 作成するAPIのエンドポイント一覧
今回は基本的なCRUDのAPIを実装していきます。

| メソッド | エンドポイント | 説明 |
| --- | --- | --- |
| `GET` | `/todos` | すべてのTODOを取得 |
| `GET` | `/todos/:id` | 特定のTODOを取得 |
| `POST` | `/todos` | 新しいTODOを作成 |
| `PUT` | `/todos/:id` | TODOを更新 |
| `DELETE` | `/todos/:id` | TODOを削除 |

### プロジェクトのセットアップ
プロジェクトの命名は各自に任せますが今回はシンプルに`go-todo-app`という命名にしています。
```bash
$ mkdir go-todo-app
$ go mod init go-todo-app
go: creating new go.mod: module go-todo-app
```
これで準備ができたのでTODO APIを作っていきましょう！

# 1章 HTTPサーバーについて

- 第1章では`net/http`という標準パッケージを使用してHTTPサーバーをまず実装しその後Echoを使って書き換えていきたいと思います

## **1. `net/http` を使った基本的なTODO APIの作成**

### 1-1. 基本のHTTPサーバー

- まずはHTTPのリクエストを受け取ったら、`Hello World!`とレスポンスを返すHTTP サーバーを実装してみたいと思います

```go
package main

import (
	"io"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/hello", helloHandler)
	log.Println("server start at port 8080")
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func helloHandler(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "Hello, world!")
}

```

```go
$ go run main.go
2025/02/11 16:04:52 server start at port 8080
```

```shell
$ curl http://localhost:8080/hello
Hello, world!
```

**コード解説**

簡単に解説をしていきます。

**① ハンドラの定義**

```go
func helloHandler(w http.ResponseWriter, req *http.Request) {
	// ハンドラ内で行う処理
}
```

- ここではHTTPリクエストを受け取りそのリクエストに対してHTTPレスポンスの内容をコネクションに書き込むハンドラを定義しています
- Goでハンドラを作る場合は、
    - 引数に`http.ResponseWriter` 型と `http.Request` 型をとり戻り値なし

という形にする必要があります。

**② ハンドラ内でのレスポンスの処理**

先ほど定義した`helloHandler`ハンドラの中で「どのようなレスポンスを返すのか」を定義していきます。

```go
func helloHandler(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "Hello, world!")
}
```

Go のハンドラ関数では、

1. `req *http.Request` からリクエストの情報を取得し、レスポンスの内容を決める
2. `w http.ResponseWriter` にレスポンスの内容を書き込む

という手順でレスポンスを返します。

ただ、今回の`helloHandler` 関数では、リクエストの内容に関係なく、常に "Hello, World!" を返すシンプルな実装になっているため、1 は省略されています。

2について、レスポンスの書き込みには `io.WriteString` 関数を使用しています。

```go
// io.WriteString 関数の実際の定義
func WriteString(w Writer, s string) (n int, err error)
```

`io.WriteString` 関数を見ていくと、この関数は、第一引数 `w`（`io.Writer` 型）に、第二引数 `s`（文字列）を書き込むという挙動をしています。

これを`helloHandler` に適用すると、`w（http.ResponseWriter型）` に `"Hello, World!"` という文字列を `io.WriteString` を使って書き込むことで、クライアントに `"Hello, World!"` というレスポンスが返されるようになっています。

```go
func helloHandler(w http.ResponseWriter, req *http.Request) {
		// 第1引数として渡されていた変数 w（http.ResponseWriter型） に
		// "Hello, World!"と書き込む
    io.WriteString(w, "Hello, world!")
}
```

**インターフェースについて**

ここで `io.WriteString` の第一引数 `w` は `io.Writer` 型ですが、実際には `http.ResponseWriter` 型の変数 `w` を渡しています。「型が違うのに、なぜ渡せるの？」と疑問に思うかもしれませんが、これは **インターフェース型**を利用しているからです。

`io.Writer` は、以下のように定義されています。

```go
type Writer interface {
    Write(p []byte) (n int, err error)
}
```

この定義は、「`Write(p []byte) (n int, err error)` メソッドを持つ型であれば、`io.Writer` として扱うことができる」という意味です。

例えば、以下のような構造体`Struct1` は `Write` メソッドを持っているため、`io.Writer` 型の変数に代入することができます。

```go
type Struct1 struct{}

func (t Struct1) Write(p []byte) (n int, err error) {
    // 省略
    return len(p), nil
}

var w io.Writer
w = Struct1{} // OK
```

一方、`Write` メソッドを持たない構造体 `Struct2` は `io.Writer` 型として扱うことができません。

```go
type Struct2 struct{}
var w io.Writer
w = Struct2{} // コンパイルエラー
```

今回の場合だと`http.ResponseWriter` は `Write(p []byte) (n int, err error)` メソッドを持っているため、`io.Writer` **インターフェースを満たしており**、`io.WriteString` の第一引数として使用することができるのです。

**③ 定義したハンドラを登録**

`helloHandler` を定義しただけでは、サーバーはそのハンドラを認識しません。

サーバーにハンドラを登録するには、`net/http` パッケージの `HandleFunc` 関数を使用します。

```go
http.HandleFunc("/hello", helloHandler)
```

`/hello` というパスにリクエストが来たときに `helloHandler` が実行されるようになります。

**④ サーバーの起動**

サーバーを起動するには、`net/http` パッケージの `ListenAndServe` 関数を使用します。

```go
err := http.ListenAndServe(":8080", nil)
if err != nil {
  log.Fatal(err)
}
```

http.ListenAndServeの第一引数でサーバーの起動場所を定義しています。今回は`8080`を指定しています。http.ListenAndServeでは戻り値が`error`なので、エラーが発生した場合には `log.Fatal` でログを出力してプログラムを終了するようにしています。

**まとめ**

ここまで解説したものをまとめると以下のようになります。

```go
package main

import (
	"io"
	"log"
	"net/http"
)

func main() {
	// ハンドラ関数を /hello というパスに登録
	http.HandleFunc("/hello", helloHandler)

	// サーバーが起動したことをログに出力
	log.Println("server start at port 8080")

	// サーバーを 8080 ポートで起動し、エラーが発生したらログを出力
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal(err)
	}
}

// ハンドラ関数の定義
func helloHandler(w http.ResponseWriter, req *http.Request) {
	// HTTP レスポンスとして "Hello, world!" を返す
	io.WriteString(w, "Hello, world!")
}
```

### 1-2. 許可するHTTPメソッドの指定

基本のHTTPサーバーの実装方法が分かったので次は許可するHTTPメソッドの指定を行なっていきます。

現状の`helloHandler`関数ではどのHTTPリクエストを受け付けるようになってしまっています。

```go
$ curl http://localhost:8080/hello -X GET
Hello, world!

$ curl http://localhost:8080/hello -X POST
Hello, world!

$ curl http://localhost:8080/hello -X PUT
Hello, world!

$ curl http://localhost:8080/hello -X DELETE
Hello, world!
```

特定のHTTPメソッドだけ受け付けるように実装していきましょう。

実装方針としては以下です。

1. リクエストの内容からどのHTTPメソッドかを判断
2. 定義したHTTPメソッドだった場合は正常応答を返却
3. 定義したHTTPメソッドではない場合はエラーを返却

それでは実装していきます。

まず1の「リクエストの内容からどのHTTPメソッドかを判断」はハンドラの第二引数の`req`を参照することで実現できます。

```go
func helloHandler(w http.ResponseWriter, req *http.Request) {
	// reqにリクエスト情報が入っている
}
```

`http.Request`の構造体を見ていくとMethodを持っています。

```go
type Request struct {
	// Method specifies the HTTP method (GET, POST, PUT, etc.).
	// For client requests, an empty string means GET.
	//
	// Go's HTTP client does not support sending a request with
	// the CONNECT method. See the documentation on Transport for
	// details.
	Method string
}
```

試しにログを仕込んでサーバーを起動しGETリクエストをしてみます。

```go
func helloHandler(w http.ResponseWriter, req *http.Request) {
	// io.WriteString(w, "Hello, world!")
	fmt.Println("request: ", req.Method)
}
```

ログを確認すると、GETリクエストが確認できました。

```go
$ curl http://localhost:8080/hello -X GET

// ログ
2025/02/15 17:10:32 server start at port 8080
request:  GET
```

この`req.Method`を使用すれば1と2が一気に実装できそうなので実装していきます。

```go
func helloHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method == http.MethodGet {
		io.WriteString(w, "Hello, world!")
	}
}
```

`http.MethodGet` は`net/http`パッケージの中にHTTPメソッドを定義する定数が存在するので表記揺れや仕様変更が起きた場合でも対応できるようにその定数を使用しています。

これで1と2が実装できたので最後に3の「定義したHTTPメソッドではない場合はエラーを返却」を実装していきます。

```go
func helloHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method == http.MethodGet {
		io.WriteString(w, "Hello, world!")
	} else {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
	}
}
```

シンプルにエラーを返却しても良かったのですが、`net/http` パッケージに指定した任意のエラーコードを返却する`http.Error`関数が存在するのでそちらを使用しています。

`http.Error` 関数は第2引数で指定したエラーメッセージを第3引数で指定したエラーのステータスコードと共に返却することができる関数です。

今回は405エラーを返すように実装しました。

動作確認をしていきましょう。

```go
$ curl http://localhost:8080/hello -X GET -w '%{http_code}\n'
Hello, world!
200

$ curl http://localhost:8080/hello -X PUT -w '%{http_code}\n'
Method not allowed
405

$ curl http://localhost:8080/hello -X POST -w '%{http_code}\n'
Method not allowed
405

$ curl http://localhost:8080/hello -X DELETE -w '%{http_code}\n'
Method not allowed
405
```

curl コマンドにオプションをつけることで、返却されたステータスコードが分かるようにしています。GETリクエストのときにだけ正常応答である200が返却されていて、他は405ステータスコードが返ってきていることを確認することができました。

### 1-3. (実践演習) TODO APIの各エンドポイントを実装

ここまで解説したことを使って今回作成するTODO APIの各エンドポイントを実装していきます。

**エンドポイント一覧**

| メソッド | エンドポイント | 説明 |
| --- | --- | --- |
| `GET` | `/todos/list` | すべてのTODOを取得 |
| `GET` | `/todos/:id` | 特定のTODOを取得 |
| `POST` | `/todos` | 新しいTODOを作成 |
| `PUT` | `/todos/:id` | TODOを更新 |
| `DELETE` | `/todos/:id` | TODOを削除 |

実装方針としては以下です。

1. 各エンドポイントのハンドラを作成
2. 任意のHTTPメソッドのみを許可するように
3. 許可したHTTPメソッドでない場合は405エラーを返却するように
4. エンドポイント`/todos/:id` のパスパラメータに関してはここでは一旦適当なIDを指定しておく
    1. パスパラメータの扱いに関しては後ほど解説します

4を踏まえて改めてここで実装するエンドポイントは以下です

| メソッド | エンドポイント | 説明 |
| --- | --- | --- |
| `GET` | `/todos/list` | すべてのTODOを取得 |
| `GET` | `/todos/1` | ID1のTODOを取得 |
| `POST` | `/todos` | 新しいTODOを作成 |
| `PUT` | `/todos/2` | ID2のTODOを更新 |
| `DELETE` | `/todos/3`  | ID3のTODOを削除 |

それでは実装してみてください。解説した内容のみなのでそこまで難しくはないはず！

- [実装例](https://github.com/aaaasahi/go-todo-app/pull/1/commits/64d8a32d129784ce38d19ad322cb36acfe5dd38e)

ここまでで処理が煩雑になってきたのでハンドラを別ファイルに切り出してリファクタもしてみましょう。

- [実装例](https://github.com/aaaasahi/go-todo-app/pull/1/commits/f41543475f41883d244bccd74ab116cce51ebdb9)

これで基本のTODO APIの実装ができました！

## **2. Echoを使ったHTTPサーバー**

ここまでは標準パッケージである`net/http` パッケージを使用してきましたが、ここからは[Echo](https://echo.labstack.com/)を使用していきます。

Echoとは、Go のWEBフレームワークで`net/http` と比較するとシンプルかつ直感的で高速なのでEchoを使用することで簡単にHTTPサーバーを作ることができます。

Echoの詳細な解説については他の方が解説してくれている記事がたくさんあるのでそちらを参考にしてみてください。

### 2-1 Echoの導入と基本の実装

それではまずEchoをインストールしていきます。

```shell
$ go get github.com/labstack/echo/v4
```

インストールできたら一番最初に実装したHTTP レスポンスとして `"Hello, world!"`を返すサーバーをEchoを使用して実装していきます。

実装例

```go
package main

import (
	"github.com/labstack/echo/v4"
	"net/http"
)

func main() {
	e := echo.New()

	e.GET("/hello", helloHandler)

	e.Logger.Fatal(e.Start(":8080"))
}

func helloHandler(c echo.Context) error {
	return c.String(http.StatusOK, "Hello, world!")
}

```

`net/http` との違いに触れながら簡単に解説していきます。

**① Echoのインスタンスを作成**

```go
e := echo.New()
```

この作成したインスタンスを使ってルーティングの設定やサーバーの起動など諸々行うわけです。

echoを使用するためのおまじないくらいに思っておくのが良さそうです。

**② ルーティングの設定**

```go
e.GET("/hello", helloHandler)
```

`e.GET()` を使って `/hello` にGETリクエストが来たときに `helloHandler` を実行 するように設定しています。

`net/http` パッケージでは、HTTPメソッドで分岐する実装にしていましたが、EchoではルーティングにHTTPメソッドを指定することで指定したHTTPメソッド以外は405をレスポンスするようになっているので直感的で便利ですね！

HTTPメソッドで分岐をしていた例

```go
func helloHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method == http.MethodGet {
		io.WriteString(w, "Hello, world!")
	} else {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
	}
}
```

**③ ハンドラ関数の定義**

```go
func helloHandler(c echo.Context) error {
	return c.String(http.StatusOK, "Hello, world!")
}
```

`net/http`パッケージで実装したハンドラと比べてみると、まずパラメータが`w http.ResponseWriter`、`req *http.Request`から`c echo.Context`になっています。

元の実装：

```go
func helloHandler(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "Hello, world!")
}
```

`echo.Context` とは一言で言うとリクエストとレスポンスの情報をまとめて管理しているコンテキストで`http.ResponseWriter`と`http.Request`を内包しているようなイメージです。

例えばリクエストを取得したい場合は、`Context`インターフェースの`Request()` メソッドを使用することで取得できたりします。

今回は`io.WriteString` と同様テキストでのレスポンスを返したいので`Context` インターフェースの`String()`メソッドを使用して`Hello, world!` がレスポンスされるようにしています。

### 2-2. (実践演習) Echoを使用してTODO APIのエンドポイントを書き換え

基本的なEchoの使い方が分かったところで、`net/http`パッケージを使用して「1-3. (実践演習) TODO APIの各エンドポイントを実装」で実装した各エンドポイントを書き換えていきましょう。

- [実装例](https://github.com/aaaasahi/go-todo-app/pull/1/commits/a12b1948a58e20ee24ea161124ec18a91c755fb2)

`net/http`パッケージで実装したものと比べるとかなりスッキリしたかと思います。

## **3. パスパラメータとクエリパラメータの取得**

### 3-1. パスパラメータの取得方法

最後にパスパラメータの取得方法を学び暫定で設定した以下のエンドポイントに反映していきます。

| メソッド | エンドポイント | 説明 |
| --- | --- | --- |
| `GET` | `/todos/1` | ID1のTODOを取得 |
| `PUT` | `/todos/2` | ID2のTODOを更新 |
| `DELETE` | `/todos/3`  | ID3のTODOを削除 |

echoを使用してのパスパラメータの取得はとても簡単です。

echoでは`Context`インターフェースが提供している`Param()` というメソッドを使用することでパスパラメータから値を取得することができます。

例えばhelloHandlerでnameというパスパラメータを取得したい場合は以下のように実装します

例：`/hello/:name` の場合

```go
package main

import (
	"github.com/labstack/echo/v4"
	"net/http"
)

func main() {
	e := echo.New()
	// nameを指定
	e.GET("/hello/:name", helloHandler)

	e.Logger.Fatal(e.Start(":8080"))
}

func helloHandler(c echo.Context) error {
	// パスパラメータ `name` を取得
	name := c.Param("name") 
	return c.String(http.StatusOK, "Hello, " + name + "!")
}
```

注意しないといけないのは`Param()`メソッドの実装を見ると`string`を返り値にしていることです。

```go
func (c *context) Param(name string) string {
}
```

つまりクエリパラメータを数値として取得したい場合は`strconv.Atoi()`を使用して数値型に変換する必要があります。

例：`/hello/:id` の場合

```go
package main

import (
	"github.com/labstack/echo/v4"
	"net/http"
	"strconv"
)

func main() {
	e := echo.New()
	// idを指定
	e.GET("/hello/:id", helloHandler)

	e.Logger.Fatal(e.Start(":8080"))
}

func helloHandler(c echo.Context) error {
	// パスパラメータ `id` を取得
	idStr := c.Param("id") 
	id, err := strconv.Atoi(idStr)
	if err != nil {
		return c.String(http.StatusBadRequest, "Invalid ID")
	}
	return c.String(http.StatusOK, "Your ID is " + strconv.Itoa(id))
}
```

ちなみにクエリパラメータの取得をnet/httpパッケージのみで実現しようとした場合はURL解析を手動で行う必要があるので`Param()` はとてもシンプルですね。

```go
func helloHandler(w http.ResponseWriter, r *http.Request) {
	// パスから `name` を取得（正規表現やマニュアル解析が必要）
	name := strings.TrimPrefix(r.URL.Path, "/hello/")
	if name == "" {
		http.Error(w, "Name is required", http.StatusBadRequest)
		return
	}

	io.WriteString(w, "Hello, " + name + "!")
}
```

### 3-2. (実践演習) 暫定実装部分の書き換え

それでは`Param()`メソッドを使用して以下のエンドポイントを書き換えていきましょう。

| メソッド | エンドポイント | 説明 |
| --- | --- | --- |
| `GET` | `/todos/:id` | 特定のTODOを取得 |
| `PUT` | `/todos/:id` | TODOを更新 |
| `DELETE` | `/todos/:id` | TODOを削除 |

- [実装例](https://github.com/aaaasahi/go-todo-app/blob/feature/1_http-server/handlers/handlers.go)

### 3-3 クエリパラメータの取得方法

今回実装はしていませんがクエリパラメータの扱いについても触れておきます。

クエリパラメータに関してもechoの`Context`インターフェースが提供している`QueryParam()`メソッドを使用することで簡単に処理できます

例：`/search?keyword=xxx` 形式の場合

```go
package main

import (
	"github.com/labstack/echo/v4"
	"net/http"
)

func main() {
	e := echo.New()
	e.GET("/search", searchHandler)
	e.Logger.Fatal(e.Start(":8080"))
}

func searchHandler(c echo.Context) error {
	// クエリパラメータ `keyword` を取得
	keyword := c.QueryParam("keyword") 
	if keyword == "" {
		return c.String(http.StatusBadRequest, "Keyword is required")
	}
	return c.String(http.StatusOK, "Searching for: " + keyword)
}
```

echoの`Context`インターフェースのおかげで非常にシンプルに実装できることがよくわかったかと思います。

## 1章まとめ

この章で学んだことは以下です。

- `net/http` で基本的なサーバーを実装方法
- `interface` の役割と `http.ResponseWriter` との関係について
- Echoの導入と基本的な実装方法
- `net/http` とEchoの違いについて
- パスパラメータやクエリパラメータの処理方法

第2章では構造体とJSONの扱いについて学んでいきましょう！

# 2章 構造体とjsonの扱い方

1章ではリクエストを受け取ったら文字列をレスポンスとして返すAPIを作成しました。
APIでは基本jsonを使用するので2章では1章で作ったAPIをjsonでやりとりできるようにまずはGoの標準パッケージである`encoding/json` を使用してjsonの扱いを学び、その後1章同様Echoでのjsonの扱い方を学び書き換えを行なっていきます。

## 1. 構造体の定義

jsonを使えるようにする前にまずはどのようなjsonの形にしてデータを扱うかを考える必要があります。
Goの構造体を使用してTODO APIで使用するデータ型を定義していきましょう。

### 1-1. データ型の定義

構造体を定義する前にまずは各エンドポイントでどのようなデータをレスポンスするのかを簡単に整理しました。

| エンドポイント | 概要 | レスポンスに含めたい内容 |
| --- | --- | --- |
| `GET /todos/list` | すべてのTODOを取得 | TODOの一覧 |
| `GET /todos/:id` | 特定のTODOを取得 | TODOの内容 |
| `POST /todos` | 新しいTODOを作成 | 作成に成功したTODOの内容 |
| `PUT /todos/:id` | TODOを更新 | 更新に成功したTODOの内容 |
| `DELETE /todos/:id` | TODOを削除 | なし |

今回はシンプルなAPIなのでレスポンスに含めるのはTODOのみで良さそうですね。
だ本来であればTODOに紐付いた情報（ex. TODOが持つコメントなど）を持つこともあるので構造体を定義する前にどのようなデータをレスポンスするのかは毎回整理することをお勧めします。

## 1-2. 構造体を定義

整理した結果必要なのはTODOのみということがわかりました。

次にTODOにどのようなフィールドを持たせるのかを考えていきます。

シンプルなTODOなので

- TODOのID
- TODOのタイトル
- TODOの内容
- TODOの作成日時
- TODOの更新日時

を持つフィールドのTODO構造体を定義すれば良さそうです。

定義すると以下のようになります

```go
type Todo struct {
    ID        int
    Title     string
    Content   string
    CreatedAt time.Time
    UpdatedAt time.Time
}
```

構造体を使った変数がどのようなものになるのか、試しに簡単なデータを作って実際に見てみましょう。

```go
package main

import (
	"fmt"
	"time"
)

type Todo struct {
	ID        int
	Title     string
	Content   string
	CreatedAt time.Time
	UpdatedAt time.Time
}

func main() {
	todo1 := Todo{
		ID:        1,
		Title:     "todo1 title",
		Content:   "todo1 content",
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	todo2 := Todo{
		ID:        2,
		Title:     "todo2 title",
		Content:   "todo2 content",
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	fmt.Printf("%+v\n", todo1)
	fmt.Printf("%+v\n", todo2)
}
```

```shell
$ go run main.go
{ID:1 Title:todo1 title Content:todo1 content CreatedAt:2025-03-01 13:49:04.11412 +0900 JST m=+0.000410335 UpdatedAt:2025-03-01 13:49:04.114121 +0900 JST m=+0.000410460}
{ID:2 Title:todo2 title Content:todo2 content CreatedAt:2025-03-01 13:49:04.114121 +0900 JST m=+0.000410543 UpdatedAt:2025-03-01 13:49:04.114121 +0900 JST m=+0.000410626}
```

このように構造体型の変数を作ることができ、フィールドに値が埋め込まれていることも確認できました。

## 2. 構造体をjsonに変換 (エンコード)

構造体が作れたのでこのTODO構造体をjsonに変換していきましょう。

まずはGoの標準パッケージである`encoding/json` パッケージで構造体を使用してjsonへ変換する方法を学びその後Echoの場合での変換方法を学んで書き換えを行なっていきます。

### 2-1. `json.Marshal` 関数でのエンコード

`encoding/json` パッケージを用いての変換では`json.Marshal`関数を使用します。

```go
func Marshal(v any) ([]byte, error)
```

`json.Marshal`関数は引数に構造体を渡すことで渡した構造体をjson形式に変換を行い結果をバイトのスライスとして返します（失敗したらエラーをreturnする）。

ちなみに関数の引数が`any`型となっているので、どんな型でも引数に渡すことができます。

実際に使用方法を見ていきましょう。

```go
package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type Todo struct {
	ID        int
	Title     string
	Content   string
	CreatedAt time.Time
	UpdatedAt time.Time
}

func main() {
	todo1 := Todo{
		ID:        1,
		Title:     "todo1 title",
		Content:   "todo1 content",
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	
	jsonData, err := json.Marshal(todo1)
	if err != nil {
		fmt.Println(err)
		return
	}
	
	// 文字列に変換して出力
	fmt.Println(string(jsonData))
}

```

実行してみると

```shell
$ go run main.go
// 整形してます
{
	"ID": 1,
	"Title": "todo1 title",
	"Content": "todo1 content",
	"CreatedAt": "2025-03-01T14:53:16.427823+09:00",
	"UpdatedAt": "2025-03-01T14:53:16.427823+09:00"
}
```

実行結果を見るときちんとjsonにエンコードができているのがわかります。

### 2-2. jsonタグの追加

出力されたjsonを見ると、Goの構造体のフィールド名（キャメルケース）で出力されています。

```go
{
	"ID": 1,
	"Title": "todo1 title",
	"Content": "todo1 content",
	"CreatedAt": "2025-03-01T14:53:16.427823+09:00",
	"UpdatedAt": "2025-03-01T14:53:16.427823+09:00"
}
```

jsonのキー名をキャメルケースにするかスネークケースにするかは賛否両論ありますが、今回作るAPIではjsonのキー名はスネークケースで統一していきたいので追加で設定を加えていきます。

Goの構造体にjsonタグを追加することでエンコード時のjsonのキー名をカスタマイズできます。

jsonタグを追加した構造体は以下です。

```go
type Todo struct {
	ID        int       `json:"id"`
	Title     string    `json:"title"`
	Content   string    `json:"content"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}
```

実際にキー名が変更されているか出力してみましょう

```go
package main

import (
	"encoding/json"
	"fmt"
	"time"
)

// jsonタグを追加
type Todo struct {
    ID        int       `json:"id"`
    Title     string    `json:"title"`
    Content   string    `json:"content"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

func main() {
	todo1 := Todo{
		ID:        1,
		Title:     "todo1 title",
		Content:   "todo1 content",
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	
	jsonData, err := json.Marshal(todo1)
	if err != nil {
		fmt.Println(err)
		return
	}
	
	fmt.Println(string(jsonData))
}

```

実行結果

```shell
$ go run main.go
{
	"id": 1,
	"title": "todo1 title",
	"content": "todo1 content",
	"created_at": "2025-03-07T16:32:49.593819+09:00",
	"updated_at": "2025-03-07T16:32:49.593819+09:00"
}
```

無事指定したキー名で出力することができました。

### 2-3. Echoの場合でのエンコードの仕方(`c.JSON`)

`encoding/json`パッケージを使用する方法を紹介しましたが、Echoではより簡単にjsonをエンコードすることができます。

Echoの`echo.Context`には、JSONレスポンスを簡単に返せる`c.JSON`メソッドがあります。このメソッドを使えば、`json.Marshal`を使わずに、構造体を直接JSONとして返すことができます。

```go
func (c *Context) JSON(code int, i interface{}) error 
```

`c.JSON`メソッドでは内部で`encoding/json` パッケージのエンコード処理を行う`json.NewEncoder`という関数を使用していて、実質`json.Marshal` と同じような挙動をしています。

`c.JSON`メソッドが便利な点としては、レスポンスの書き込みだけでなく本来別で実装する必要があるレスポンスヘッダーの設定やHTTPステータスコードの設定も内部で行なっているのでEchoを使う場合は基本的に `c.JSON` を使用するのがベストプラクティスです。

`c.JSON`の便利さがわかったところで実際に実装例を見ていきましょう。

```go
package main

import (
	"net/http"
	"time"

	"github.com/labstack/echo/v4"
)

type Todo struct {
	ID        int       `json:"id"`
	Title     string    `json:"title"`
	Content   string    `json:"content"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

func getTodoHandler(c echo.Context) error {
	todo := Todo{
		ID:        1,
		Title:     "Sample Todo",
		Content:   "This is a sample task",
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	// JSONレスポンスとして返す
	return c.JSON(http.StatusOK, todo)
}

func main() {
	e := echo.New()

	e.GET("/todos/:id", getTodoHandler)

	e.Start(":8080")
}

```

```shell
$ curl -X GET http://localhost:8080/todos/1
{
	"id": 1,
	"title": "Sample Todo",
	"content": "This is a sample task",
	"created_at": "2025-03-07T16:32:49.593819+09:00",
	"updated_at": "2025-03-07T16:32:49.593819+09:00"
}
```

引数にレスポンスするステータスコードとjsonに変換したい構造体を指定するだけでjsonレスポンスとして返すことができます。

## 3. jsonを構造体に変換 (デコード)

構造体をjsonに変換する方法について学んだので次はその逆のjsonを構造体に変換（デコード）する方法について学んでいきます。

こちらもエンコードの時と同様にまずは`encoding/json` パッケージで提供されている方法を学びその後Echoで提供されているメソッドでの書き換えを行なっていきます。

### 3-1. `json.UnMarshal` 関数でのデコード

`encoding/json`パッケージでは、`json.Unmarshal`関数を使用することでJSONデータをGoの構造体に変換することができます。

```go
func Unmarshal(data []byte, v any) error
```

この関数は、JSONデータであるバイトスライスとGoの構造体（v）の二つを引数に持ちます。

関数内では、引数として渡されたJSONデータのキーと構造体のフィールド名を比較し、一致するフィールドに対応する値を格納する処理を行ってます。その過程で失敗した場合はエラーを返すようになっています。

実際に使用例を見ていきましょう。

```go
package main

import (
	"encoding/json"
	"fmt"
)

type Todo struct {
	ID      int    `json:"id"`
	Title   string `json:"title"`
	Content string `json:"content"`
}

func main() {
	// JSONデータ
	jsonData := `{"id": 1, "title": "Sample Todo", "content": "This is a sample task"}`

	var todo Todo

	// JSONデコード（変換）
	err := json.Unmarshal([]byte(jsonData), &todo)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// デコード結果を表示
	fmt.Printf("Decoded Struct: %+v\n", todo)
}

```

実行結果

```go
$ go run main.go
Decoded Struct: {ID:1 Title:Sample Todo Content:This is a sample task}
```

このように、JSONデータをGoの構造体にデコードすることができました。

`json.Unmarshal` の注意点

**① デコード先の変数はポインタで渡す**

`json.Unmarshal` はJSONデータをデコードして構造体のフィールドに直接値をセットするためポインタで渡す必要があります。

```go
var todo Todo
json.Unmarshal([]byte(jsonData), todo)  // NG
json.Unmarshal([]byte(jsonData), &todo) // OK
```

このあたりの説明は過去にポインタの仕組みについて解説しているのでそちらを見ていただけるとよりわかりやすいかと思います。

[**Goのポインタについて改めて理解する**](https://qiita.com/aaaasahi_17/items/0acfd2352771fd3d8e8f)

**② 不要なJSONキーは無視される**

`json.Unmarshal`ではJSONのキーと構造体のフィールド名は一致している必要があります。

構造体に定義されていないフィールドがJSONデータに含まれていても、エラーにはならず無視されます。

```go
jsonData := `{"id": 1, "title": "Sample Todo", "content": "This is a sample task", "extra": "This field is ignored"}`
```

仮に上記のようなJSONデータをデコードした場合、`extra`フィールドは構造体には存在しないためデコードしても無視されます。

### 3-2. Echoの場合でのデコードの仕方(`c.Bind`)

続いてEchoの場合でのデコードの仕方を見ていきましょう。

EchoではリクエストのJSONデータを簡単にGoの構造体にデコードする方法として`c.Bind`メソッドが提供されています。

```go
func (c *Context) Bind(i interface{}) error
```

`c.Bind`メソッドでは内部で`encoding/json` パッケージのデコード処理を行う`json.NewEncoder`という関数を使用していて、実質`json.Unmarshal` と同じような挙動をしています。

`c.Bind` が便利な点としては、リクエストの `Content-Type` によって適切なデコーダーを選択してデコードを行なってくれます。

今回はJSONなので`Content-Type`は`application/json`となり`application/json`用のデコーダーである`json.Unmarshal` （正確には`json.NewEncoder`）が選択されデコードを行なっています。

それでは実際に`c.Bind`の使用例を見ていきましょう。

今回はPOSTリクエストのリクエストボディをGoの構造体にデコードしてそのままレスポンスしています。

```go
package main

import (
	"net/http"

	"github.com/labstack/echo/v4"
)

type Todo struct {
	ID      int    `json:"id"`
	Title   string `json:"title"`
	Content string `json:"content"`
}

func createTodoHandler(c echo.Context) error {
	var todo Todo

	// JSONデータを構造体にバインド
	if err := c.Bind(&todo); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid JSON format"})
	}

	// デコードされたデータをレスポンスとして返す
	return c.JSON(http.StatusOK, todo)
}

func main() {
	e := echo.New()

	e.POST("/todos", createTodoHandler)

	e.Start(":8080")
}

```

実際にサーバーを起動してリクエストを送ってみます。

```shell
$ curl -X POST -H "Content-Type: application/json" -d '{"id":1, "title":"Sample Todo", "content":"This is a sample task"}' http://localhost:8080/todos
{
	"id": 1,
	"title": "Sample Todo",
	"content": "This is a sample task"
}
```

`c.Bind`を利用すると簡単にリクエストボディのJSONをデコードできることがわかりました。

## 4. (実践演習) TODO APIに実装

これまで学んだ内容をTODO APIに実装していきましょう。

### 4-1. 事前準備

実際に実装に移る前に以下の事前準備を行なっていきます。

1. データモデルの定義
2. モックデータを用意

**データモデルの定義**

まずはデータモデル用のパッケージを作成してそこに先ほど作ったTodo構造体を定義していきます。

新しくmodelsディレクトリをその中に`models.go`というファイルを作成して構造体を定義していきます。

- [実装例](https://github.com/aaaasahi/go-todo-app/pull/2/commits/dc0becb3b56bee2b3360947811b097455f08891f)

**モックデータを用意**

データモデルが定義できたらモックデータを用意していきます。

本来のAPIではリクエストを受けたらデータベースからデータを取得してそれをレスポンスに入れる必要がありますが、現在時点ではデータベース周りの機能の実装は行っていないのでモックデータを用意する必要があります。

先ほど作成したmodelsディレクトリの中に`mock_data.go`というモックデータ用のファイルを作成してそこにモックデータを定義しておきます。

- [実装例](https://github.com/aaaasahi/go-todo-app/pull/2/commits/21b43dc3d7453125341c1b48072638e37ae99910)

### 4-2. 実装

事前準備が完了したので全てのエンドポイントに対して実装を行います。

各リクエストごとに簡単に処理の流れを書くのでそちらを参考にして実装してみてください。

- 各コミットごとに実装例を貼りましたが全体で見たい場合は[こちら](https://github.com/aaaasahi/go-todo-app/blob/feature/2_struct_json/handlers/handlers.go)

**POSTリクエスト**

1. リクエストボディからTodoデータをmodels.Todo構造体にバインドする
    1. 失敗した場合は500エラーをレスポンスする
2. 成功した場合は、TodoデータをJSONレスポンスとして返すように

- [実装例](https://github.com/aaaasahi/go-todo-app/pull/2/commits/ed70bfd6a587ca1cb3c089d06a8a56a7a544e080)

**PUTリクエスト**

1. パスパラメータから更新対象のIDを取得
    1. IDが不正な場合は400エラーをレスポンスする
2. リクエストボディからTodoデータをmodels.Todo構造体にバインドする処理を追加
    1. 失敗した場合は500エラーをレスポンスする
3. バインドしたデータにURLパラメータから取得したIDを設定
4. 更新されたTodoデータをJSONレスポンスとして返すように

- [実装例](https://github.com/aaaasahi/go-todo-app/pull/2/commits/5590298cfeb91927846c2d26abd0f4bd57774dd1)

**GETリクエスト（全件取得）**

1. モックデータ（Todo1とTodo2）を配列に格納
2. モックデータをJSONレスポンスとして返す

- [実装例](https://github.com/aaaasahi/go-todo-app/pull/2/commits/65c6eaa0e2401d732a43d63a73222164f3f09d45)

**GETリクエスト（IDから取得）**

1. パスパラメータからIDを取得
    1. IDが不正な場合は400エラーをレスポンスする
2. 取得したIDを暫定でログ出力
3. モックデータをJSONレスポンスとして返却する

- [実装例](https://github.com/aaaasahi/go-todo-app/pull/2/commits/65c6eaa0e2401d732a43d63a73222164f3f09d45)

**DELETEリクエスト**

1. パスパラメータから削除対象のIDを取得する
    1. IDが不正な場合は400エラーをレスポンスする
2. 成功した場合は削除完了メッセージをJSONレスポンスとして返却する

- [実装例](https://github.com/aaaasahi/go-todo-app/pull/2/commits/286281be637402ec3c6d95323cb2d2d3787c5683)

最後に動作確認をしてみましょう。

動作確認用のcurlを書いておくので参考にしてみてください！

```shell
# POST
curl -X POST http://localhost:8080/todos \
-H "Content-Type: application/json" \
-d '{
    "title": "新しいTODO",
    "content": "これは新しいTODOのコンテンツです"
}'

# PUT
curl -X PUT http://localhost:8080/todos/1 \
-H "Content-Type: application/json" \
-d '{
    "title": "更新されたTODO",
    "content": "これは更新されたTODOのコンテンツです"
}'

# GET（全件取得）
curl -X GET http://localhost:8080/todos

# GET（1件取得）
curl -X GET http://localhost:8080/todos/1

# DELETE
curl -X DELETE http://localhost:8080/todos/1
```

## 2章まとめ
この章でで学んだことは以下です。

- Goの構造体について
- 構造体をjsonにエンコードする方法
    - `json.Marshal` とEchoの`c.JSON` の違い
- jsonを構造体にデコードする方法
    - `json.Unmarshal` とEchoの`c.Bind` の違い

# 3章 DBとの接続
TODOのデータの保存や取得をするにはTODO APIとDBを接続する必要があります。

Goの標準パッケージには`database/sql` がありますが今回は`database/sql` をラップしたライブラリである[sqlx](https://github.com/jmoiron/sqlx)を使用していきます。

`sqlx`は`database/sql` と比べて構造体へのマッピングやクエリの実行、ポインタでNull値を扱えるなど直感的かつ簡潔に書けることが多いので筆者もよく使用しています。

## 1.  DBのセットアップ

### 1-1. DBの用意

今回使用するDBはMySQLを使用します。MySQLの環境は、ローカルに直接インストールする方法や、Dockerコンテナを使用する方法がありますが、今回はDockerコンテナを使用した方法を紹介します。

**docker-compose.ymlを用意しMySQLを立ち上げる**

MySQLを実行するための `docker-compose.yml` を作成します。TODO APIを作成しているディレクトリに`docker-compose.yml` と環境変数を定義する`.env`ファイル、`.env` の環境変数をgitで管理したくないので`.gitignore` ファイルを作成し各ファイルに諸々記入していきます。

- `docker-compose.yml`

```yaml
version: '3.8'

services:
  mysql:
    image: mysql:5.7
    container_name: go-todo-db
    environment:
      MYSQL_ROOT_USER: ${MYSQL_ROOT_USER}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    command: ["mysqld", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"]
    ports:
      - "${DB_PORT}:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - mysql_network

volumes:
  mysql_data:

networks:
  mysql_network:
```

今回MySQLのバージョンは5.7を使用しています。

- `.env`

```
MYSQL_ROOT_USER=root
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=testdb
MYSQL_USER=docker
MYSQL_PASSWORD=password
DB_PORT=3306
```

- `.gitignore`

```
.env
```

今回はGoのAPIについての解説をメインにしているのでこの辺りの細かい解説は省きます。

コンテナを立ち上げて無事立ち上がればDBの用意はOKです

```shell
$ docker-compose up

go-todo-db  | 2025-03-14T09:37:36.427734Z 0 [Note] Event Scheduler: Loaded 0 events
go-todo-db  | 2025-03-14T09:37:36.428648Z 0 [Note] mysqld: ready for connections.
go-todo-db  | Version: '5.7.44'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
```

### 1-2. テーブル作成と初期データの投入

DBの立ち上げができたら今回使用するテーブルと初期データの投入を行なっていきます。

今回はSQLスクリプトではなくSequel Aceを使用してデータを投入していきます。

Sequel Aceのインストールについては以下を参考にしてください

- [Sequel Ace](https://sequel-ace.com/)
- https://qiita.com/ucan-lab/items/b1304eee2157dbef7774

Sequel AceがインストールできたらSequel Aceを立ち上げ以下の接続情報を入力して接続します

```
ホスト名: 127.0.0.1
ユーザー名: docker
パスワード: password
データベース: testdb
ポート: 3306
```

接続できたらクエリを流していきます。

- テーブル作成

```sql
CREATE TABLE IF NOT EXISTS todos (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```

- 初期データの登録

```sql
INSERT INTO todos (title, content, created_at, updated_at) 
VALUES 
    ('todo1 title', 'todo1 content', NOW(), NOW()),
    ('todo2 title', 'todo2 content', NOW(), NOW()),
    ('todo3 title', 'todo3 content', NOW(), NOW());

```

初期データの登録ができたか実際にSELECTして確認してみます。

```sql
SELECT * FROM todos;
```

投入したデータが取得できていたらOKです。

```sql
+----+--------------+---------------+---------------------+---------------------+
| ID | title       | content       | created_at         | updated_at         |
+----+--------------+---------------+---------------------+---------------------+
|  1 | todo1 title | todo1 content | 2024-03-14 12:00:00 | 2024-03-14 12:00:00 |
|  2 | todo2 title | todo2 content | 2024-03-14 12:00:00 | 2024-03-14 12:00:00 |
|  3 | todo3 title | todo3 content | 2024-03-14 12:00:00 | 2024-03-14 12:00:00 |
+----+--------------+---------------+---------------------+---------------------+

```

## 2.  `sqlx`を使用してDBに接続

DBの用意ができたのでDBに接続するための処理を書いていきましょう。

冒頭にあるように今回は`sqlx`を使用し、MySQLデータベースに接続する方法を解説します。

### **2-1. `sqlx`とドライバのインストール**

まずは以下を実行して`sqlx`をインストールします。

```shell
$ go get -u github.com/jmoiron/sqlx
```

Goの標準パッケージである`database/sql` パッケージや今回使用する`sqlx`ではデータベースとの接続を抽象化したインターフェースを提供しますが、実際にMySQLに接続するためには、対応するドライバを別途インストールする必要があります。

ドライバとは、データベースとアプリケーションの間で通信を行うためのソフトウェアコンポーネントのことです。

なぜドライバが必要かというと、MySQLやPostgreSQLのようにDBごとに異なる通信プロトコルや接続方法あるので、`database/sql` や`sqlx`だけでは対応できません。

なので各DBに対応したドライバを別途インストールすることで、Goのコードは `database/sql` や`sqlx`を通じて、異なるデータベースに柔軟に接続することができます。

今回のDBはMySQLを使用しているので、 `github.com/go-sql-driver/mysql` というGoでMySQLを扱う際に最も一般的に使用されてるドライバを使用します。

以下を実行してドライバのインストールを行います。

```shell
$ go get -u github.com/go-sql-driver/mysql
```

### 2-2. データベース接続処理の実装

ドライバのインストールができたので実際にDBと接続するための実装を行なっていきます。

今回の接続は`main.go`で行いたいので一旦既存の実装はコメントアウトしたうえで実装していきます。

```go
package main

import (
	"fmt"
	"log"

	"github.com/jmoiron/sqlx"
	_ "github.com/go-sql-driver/mysql"
)

const (
	dbUser     = "docker"
	dbPassword = "password"
	dbHost     = "127.0.0.1"
	dbPort     = "3306"
	dbName     = "testdb"
)

func main() {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true",dbUser, dbPassword, dbHost, dbPort, dbName)

	db, err := sqlx.Connect("mysql", dsn)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	fmt.Println("Successfully connected to the database")
}

```

簡単に解説をしていきます。

- 接続情報の定義

```go
const (
	dbUser     = "docker"
	dbPassword = "password"
	dbHost     = "127.0.0.1"
	dbPort     = "3306"
	dbName     = "testdb"
)

func main() {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true",dbUser, dbPassword, dbHost, dbPort, dbName)
}
```

`.env`の環境変数として定義したDBへの接続情報を定数として定義しそれらをまとめた変数`dsn`を定義しています。

- データベース接続と疎通確認

```go
db, err := sqlx.Connect("mysql", dsn)
if err != nil {
	log.Fatal(err)
}
defer db.Close()

fmt.Println("Successfully connected to the database")
```

`sqlx`には`Connect`関数が存在します。

```go
func Connect(driverName, dataSourceName string) (*DB, error)
```

この関数では第1引数に先ほど説明したどのDBのドライバを使用するのかを指定できます。

今回はMySQLのドライバを使用するため`"mysql"` を引数に渡しています。

そして第2引数にデータベースのアドレスを指定することで、そのデータベースに接続すること
ができます。

返り値となっている `sqlx.DB`型というのは、そのデータベースへのコネクションを管理するた
めの構造体で、今後このデータベースに対してクエリ等何かを実行したい場合には、全てこの
`sqlx.DB`型を経由して行うことになります。

`defer`で指定している`Close`関数は、プログラム実行後DBを使わない段階の場合にはDBへのコネクションを閉じる必要があるため`sqlx.DB`型の`Close`を`defer`で呼ぶことで実現しています。

ちなみにこの`Connect`関数は標準パッケージである`database/sql` には存在しない関数です。

`database/sql` でデータベース接続と疎通確認するには以下のように、

```go
db, err := sql.Open("mysql", dsn)
if err != nil {
	log.Fatal(err)
}
defer db.Close()

if err := db.Ping(); err != nil {
	log.Fatal(err)
}
fmt.Println("Successfully connected to the database")
```

`Open`関数と`Ping`関数を組み合わせて行う必要がありますが、`sqlx`の`Connect`関数ではその二つの関数を内部で実行しているので`Connect`関数のみで接続と疎通確認ができるようになっています。

MySQLのコンテナを立ち上げ、今回実装したものを実際に実行してみましょう。

実際に以下のようなメッセージが出力されれば接続は成功しています。

```shell
$ go run main.go
Successfully connected to the database
```

## 3. データの取得と挿入処理

DBと接続できるようになったので、データの取得（`SELECT`）とデータの挿入（`INSERT`）を行う処理を実装していきます。

### 3-1. データの取得（`SELECT`）

まずはデータ取得からです。

Todoの全件取得を実装してみます。

```go
func getTodos(db *sqlx.DB) ([]models.Todo, error) {
	dbTodos := []struct {
		ID        int       `db:"id"`
		Title     string    `db:"title"`
		Content   string    `db:"content"`
		CreatedAt time.Time `db:"created_at"`
		UpdatedAt time.Time `db:"updated_at"`
	}{}

	query := "SELECT id, title, content, created_at, updated_at FROM todos"

	err := db.Select(&dbTodos, query)
	if err != nil {
		return nil, err
	}

	todos := make([]models.Todo, 0)
	for _, dbTodo := range dbTodos {
		todos = append(todos, models.Todo{
			ID:        dbTodo.ID,
			Title:     dbTodo.Title,
			Content:   dbTodo.Content,
			CreatedAt: dbTodo.CreatedAt,
			UpdatedAt: dbTodo.UpdatedAt,
		})
	}
	return todos, nil
}

```

簡単に解説していきます。

- 構造体のスライスを定義

```go
dbTodos := []struct {
		ID        int       `db:"id"`
		Title     string    `db:"title"`
		Content   string    `db:"content"`
		CreatedAt time.Time `db:"created_at"`
		UpdatedAt time.Time `db:"updated_at"`
	}{}
```

ここではこの後行うクエリの実行の結果を一時的に格納する構造体のスライスを定義しています。

各フィールドにdbタグをつけることでDBのカラム名とGoの構造体のフィールドをマッピングすることができます。

ちなみに、CreatedAtとUpdatedAtで使用している`time.Time`型は日付と時刻を扱うためのGoの標準型で、`parseTime=true`パラメータをDSNに設定しているため、MySQLの`DATETIME`型からGoの`time.Time`型への自動変換が可能になっています。

- クエリの実行

```go
query := "SELECT id, title, content, created_at, updated_at FROM todos"

err := db.Select(&dbTodos, query)
if err != nil {
	return nil, err
}
```

sqlxが提供する`Select`関数を使用してクエリの実行を行っています。

```go
func (db *DB) Select(dest interface{}, query string, args ...interface{}) error
```

`Select`関数の第1引数には、結果を格納するスライスへのポインタを指定し、第2引数に実行するクエリの文字列を指定します。

今回は指定していませんが、関数の定義を見ると第3引数にクエリのパラメータも指定することができます。これは今後の実装で使用するのでぜひ覚えておいて欲しいです。

```go
db.Select(&todos, "SELECT * FROM todos WHERE id = ?", id)
```

ちなみに`Select`関数を使用せずに`database/sql` だけで実装を行うと以下のようになります。

```go
func getTodos(db *sql.DB) ([]models.Todo, error) {
    query := "SELECT id, title, content, created_at, updated_at FROM todos"
    
    // クエリを実行して結果セット（Rows）を取得
    rows, err := db.Query(query)
    if err != nil {
        return nil, err
    }
    // クローズ
    defer rows.Close()
    
    // 結果を格納するスライスを初期化
    todos := []models.Todo{}
    
    // 結果セットの各行を処理
    for rows.Next() {
        // 一時変数を宣言
        var id int
        var title, content string
        var createdAt, updatedAt time.Time
        
        // 現在の行の各カラムの値を変数にスキャン
        err := rows.Scan(&id, &title, &content, &createdAt, &updatedAt)
        if err != nil {
            return nil, err
        }
        // 取得した値からTodo構造体を作成
        todo := models.Todo{
            ID:        id,
            Title:     title,
            Content:   content,
            CreatedAt: createdAt,
            UpdatedAt: updatedAt,
        }
        // 結果スライスに追加
        todos = append(todos, todo)
    }
    
    return todos, nil
}
```

コメントにも書きましたがポイントとなる処理の流れを簡単に整理すると、

1. `Query`関数でクエリを実行し結果を取得
2. 実行結果を`Next`関数を使用して結果の各行を順番に処理
3. `Scan`関数を使用して行データのスキャン

を行なっています。

`Select`関数では上記の処理を内包しているのでコードを簡潔に書くことができます。

- 実行結果を`models.Todo`の形式に変換

```go
todos := make([]models.Todo, 0)
for _, dbTodo := range dbTodos {
	todos = append(todos, models.Todo{
		ID:        dbTodo.ID,
		Title:     dbTodo.Title,
		Content:   dbTodo.Content,
		CreatedAt: dbTodo.CreatedAt,
		UpdatedAt: dbTodo.UpdatedAt,
	})
}
return todos, nil
```

最後にクエリ実行後DBから取得したデータをアプリケーションで使用する`models.Todo`型の形式に変換しています。

ちなみになぜこのようなことを行なっているかというと、データベースの構造（テーブル設計）とアプリケーションのドメインモデル(`models.Todo`)を分離することで、仮にデータベーススキーマが変更されてもこの変換ロジックだけを修正すれば、アプリケーション全体に影響を与えないため変更に強くなるためです。

この辺りは過去にクリーンアーキテクチャについて解説した記事にも書いたのでそちらを参照して貰えばと思います。

https://qiita.com/aaaasahi_17/items/b4b4d08dbad45a270bd8

### 3-2. データの挿入（`INSERT`）

続いてデータの挿入（`INSERT`）を実装していきます。

```go
func createTodo(db *sqlx.DB, todo models.Todo) (models.Todo, error) {
	now := time.Now()

	params := map[string]interface{}{
		"title":      todo.Title,
		"content":    todo.Content,
		"created_at": now,
		"updated_at": now,
	}

	query := `
		INSERT INTO todos (title, content, created_at, updated_at) 
		VALUES (:title, :content, :created_at, :updated_at)
	`

	result, err := db.NamedExec(query, params)
	if err != nil {
		return models.Todo{}, err
	}

	id, err := result.LastInsertId()
	if err != nil {
		return models.Todo{}, err
	}

	todo.ID = int(id)

	return todo, nil
}
```

こちらも簡単に処理の解説をしていきます。

- パラメータマップを作成

```go
params := map[string]interface{}{
		"title":      todo.Title,
		"content":    todo.Content,
		"created_at": now,
		"updated_at": now,
	}
```

マップを作成せず引数の`models.Todo`をそのままクエリ実行の関数に渡しても良いのですが、各パラメータと明示的なマッピングを行いたい、`created_at`と`updated_at`に現在時刻を設定したかったためマップを作成しています。

- クエリの実行

```go
query := `
	INSERT INTO todos (title, content, created_at, updated_at) 
	VALUES (:title, :content, :created_at, :updated_at)
`

result, err := db.NamedExec(query, params)
if err != nil {
	return models.Todo{}, err
}
```

クエリの実行には`NamedExec`関数を使用しています。

`NamedExec`関数は第1引数に指定する名前付きパラメータと構造体やマップのフィールド・キーをマッピングして、SQLを実行する関数です。

```go
func (db *DB) NamedExec(query string, arg interface{}) (sql.Result, error)
```

名前付きパラメータというのは、クエリの中で `:title` や `:content` のように名前をつけて指定するパラメータのことで、名前をつけて指定することで第2引数に渡す構造体やマップと同じ名前でマッピングすることができます。

名前付きパラメータの利点としては、通常の `database/sql` や `sqlx.Exec` を使う場合、プレースホルダは `?` を使います

```sql
db.Exec("INSERT INTO todos (title, content) VALUES (?, ?)", "タイトル", "内容")
```

このような指定の仕方は値の順番に依存するため、引数の順序ミスが起きやすいためこれを防ぐことができます。

`NamedExec`関数の返り値は`sql.Result` 型となっています。

`sql.Result` 型は、SQLの実行結果に関する情報を取得するためのインターフェースです。

この後の処理に出てくる`LastInsertId`も`sql.Result` が提供するメソッドです。

つまり`sql.Result` を使うことで、実際にデータベースで何が起きたのかを確認できるようになります。

- 挿入されたIDの取得

```go
id, err := result.LastInsertId()
if err != nil {
	return models.Todo{}, err
}
```

INSERTした結果のIDを`LastInsertId`関数を使用して取得しています。

なぜIDを取得する必要があるかというと、今回テーブルを作成した時に主キーであるIDに`AUTO_INCREMENT`を指定しています。

```sql
CREATE TABLE IF NOT EXISTS todos (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```

`AUTO_INCREMENT`を指定した場合、新しい行がINSERTされると、データベースが自動的に一意のID値を生成します。

つまりこのIDはアプリケーションが指定するのではなく、データベースが決定していることになり挿入された行を後で参照するためにはIDを取得して知る必要があるためです。

## 4. (実践演習) repositoryの実装

これまでの内容を含めてTODO APIのDBアクセス周りの実装していきましょう。

その前に新しく`repositories`というディレクトリを作成しその中に`todos.go`というファイルを作成します。

repositoryとはざっくりいうとDBへのアクセス処理をビジネスロジックから切り離すためにそれらの処理を集約する役割をします。

この辺りについても先ほど貼ったクリーンアーキテクチャについて解説している記事があるので詳しくはそちらを参照して欲しいです。

https://qiita.com/aaaasahi_17/items/b4b4d08dbad45a270bd8

それでは`repositories/todos.go` に全ての処理を実装してみてください。

基本的には今回の内容だけで全て実装できるかと思います。

- [実装例](https://github.com/aaaasahi/go-todo-app/blob/fix/feature/3_connect_db/repositories/todos.go)

## 3章まとめ

この章で学んだことは以下です。

- sqlx を使ってGoからDBに接続できる
- sqlx を使ってデータの取得と挿入ができる

ここまでで前編は終了です。

後編へ続く！