[golang]golangで簡単なCRUD操作を行うREST APIを作ってみた

# はじめに
golang で簡単な CRUD 操作を行う REST API を作ってみました
go, gin, gorm, mysql を使った、簡単な TODO アプリです

## 環境
| PC  | Macbook Pro |
| ------------- | ------------- |
| OS  | Big Sur  |
| docker-compose  | 1.24.1, build 4667896b  |
| docker  | 19.03.4, build 9013bf5  |
| go  | go1.20.4 linux/amd64 |
| mysql  | 5.7 |


# 作ったもの
今回作った API の仕様は以下の通りです
簡単な TODO アプリで、 CRUD 操作の為のエンドポイントを提供します
| Method  | Path | Description | Description |
| ------------- | ------------- | ------------- | ------------- |
| GET  | /todo  | 全件取得API | TODO リストの全件取得 |
| GET  | /todo/{id}  | 取得API | 指定した TODO の取得 |
| POST  | /todo | 登録API | TODO の作成 |
| PUT  | /todo/{id}  | 更新API | TODO の更新 |
| DELETE  | /todo/{id}  | 削除API | TODO の削除 |

ソースコードはこちら

https://github.com/Ixy-194/go-api-sample-todo

## ディレクトリ構成
ディレクトリ構成はこんな感じ
一応 Clean Achitectute を意識しているつもり
```
.
├── Dockerfile.local
├── README.md
├── cmd
│   └── go-api-sample-todo
│       └── main.go
├── docker-compose.yml
├── domain
│   ├── model
│   │   └── todo.go
│   └── repository
│       └── todo.go
├── go.mod
├── go.sum
├── handler
│   ├── todo.go
│   ├── todo_test.go
│   └── validator
│       └── validator.go
├── infrastructure
│   ├── db.go
│   ├── todo.go
│   └── todo_test.go
├── migrations
│   └── 0001_create_tables.sql
└── usecase
    ├── todo.go
    └── todo_test.go
```

## 各レイヤ・ディレクトリの役割
### cmd
アプリのメインエントリが置かれるところ
### handler
HTTP リクエストを受けとり usecase を呼び出し、処理結果を返す
### usecase
ビジネスロジックを記述するところ
今回は特に複雑な処理はないので薄い実装になってます
### domain
システムが扱う業務領域に関するコードを置くところ
### repository
infrastructure 層を抽象化するための interface を定義
### model
ドメインに関する値と振る舞いをもつところ
### infrastructure
技術的な関心事を置くところ
今回だと、DB 操作周りを担っています
### migrations
初期テーブルの作成や初期データの投入のためのSQLファイルを置くところ
今回使用するテーブルの DDL を配置しています
`docker-compose.yml`に以下のような記述をすることで、初回起動時に`/migrations`内のSQLファイルを実行してくれます。初期テーブルの作成や初期データの投入に使うと便利です。
※初回コンテナ作成時にしか実行されないので、あとから DDL や DML を追加するときは、手動で実行するか、コンテナを作り直す必要があります
```yaml
mysql:
      image: mysql:5.7
      container_name: mysql-db
      ports:
        - 3306:3306
      volumes:
        - ./migrations:/docker-entrypoint-initdb.d
```

## コード
それでは実際のソースコードを順々に見ていきます
### cmd/go-api-sample-todo/main.go
今回のソースのエントリポイントです
DB の設定と、API のルーティング設定（setupRouter）を行います
web フレームワークには gin を使っています
また、DB 操作のための ORM フレームワークには gorm を使っています
```go:cmd/go-api-sample-todo/main.go
package main

import (
	"app/handler"
	"app/infrastructure"
	"app/usecase"
	"fmt"
	appvalidator "app/handler/validator"
	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

func main() {
	d, err := infrastructure.NewDB()
	if err != nil {
		fmt.Printf("failed to start server. db setup failed, err = %s", err.Error())
		return
	}
	r := setupRouter(d)
	if err := appvalidator.SetupValidator(); err != nil {
		fmt.Printf("failed to start server. validator setup failed, err = %s", err.Error())
		return
	}
	r.Run()
}

func setupRouter(d *gorm.DB) *gin.Engine {
	r := gin.Default()

	repository := infrastructure.NewTodo(d)
	usecase := usecase.NewTodo(repository)
	handler := handler.NewTodo(usecase)

	todo := r.Group("/todo")
	{
		todo.POST("", handler.Create)
		todo.GET("", handler.FindAll)
		todo.GET("/:id", handler.Find)
		todo.PUT("/:id", handler.Update)
		todo.DELETE("/:id", handler.Delete)
	}
	return r
}

```
### gin.Default()
gin.Default() は　*gin.EngineをReturnする関数で、この Engine を使って、エンドポイントの設定やミドルウェアの登録を行うことができます
例えば、以下の様な記述をすることで、「pong」を返す`/ping`というエンドポイントを作ることができます
```go
r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
```
今回の例では、更に `r.Group("/todo")`を使って、パスのグルーピングを行っています
こうすることで、共通のパスプレフィックスを持つエンドポイントを作ることができます
それぞれ実際には以下のようなエンドポイントになります
```go
    // ルーティング設定
    todo := r.Group("/todo")
	{
        todo.POST("", handler.Create)       -> /todo
		todo.GET("", handler.FindAll)       -> /todo
		todo.GET("/:id", handler.Find)      -> /todo/:id
		todo.PUT("/:id", handler.Update)    -> /todo/:id
		todo.DELETE("/:id", handler.Delete) -> /todo/:id
	}
```
また、 `todo.GET("", handler.FindAll)` の `handler.FindAll`は、
対象のエンドポイントにリクエストが来た時に呼び出す handler の関数を登録しています

## 登録API の実装
以下は、 登録 API の handler の実装です
```go:handler/todo.go
// リクエストデータを格納するための構造体
type CreateRequestParam struct {
	Task string `json:"task" binding:"required,max=60"`
}

func (t *todoHandler) Create(c *gin.Context) {
	var req CreateRequestParam
    // リクエストパラメータを構造体（CreateRequestParam）にマッピング
	if err := c.ShouldBindJSON(&req); err != nil {
        // バリデーションエラーがあった場合はエラーを返す
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
    // usecase の呼び出し
	err := t.usecase.Create(req.Task)
	if err != nil {
        // エラーがあった場合はエラーレスポンスを返却
		c.JSON(http.StatusInternalServerError, "")
		return
	}
    // レスポンスを返却
	c.JSON(http.StatusCreated, nil)
}
```
`CreateRequestParam`で、リクエストを受け取るための構造体を定義し、
リクエストパラメータのタグ名の指定と「必須項目」、「桁数60桁まで」というバリデーション設定を行っています
```go
type CreateRequestParam struct {
    // json:"task" = リクエストパラメータのタグ名の指定（指定しないと構造体名になってしまう）
    // required = 必須項目
    // max=60 = 桁数60桁まで
	Task string `json:"task" binding:"required,max=60"`
}
```
こうすることで、c.ShouldBindJSON(&req)で、構造体へのリクエストのパラメータのマッピングとバリデーションチェック行うことができます
```go
    var req CreateRequestParam
    // リクエストパラメータを構造体（CreateRequestParam）にマッピング
	if err := c.ShouldBindJSON(&req); err != nil {
        // バリデーションエラーがあった場合はエラーを返す
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
```
バリデーションエラーになった場合の実際のレスポンスはこんな感じ

```zsh:必須項目がない場合
 % curl -i localhost/todo -H "Content-Type: application/json" -X POST -d '{}'
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Date: Mon, 22 May 2023 16:37:26 GMT
Content-Length: 105

{"error":"Key: 'CreateRequestParam.Task' Error:Field validation for 'Task' failed on the 'required' tag"}
```
```zsh:桁数オーバーの場合
% curl -i localhost/todo -H "Content-Type: application/json" -X POST -d '{"task": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}'
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Date: Mon, 22 May 2023 16:38:04 GMT
Content-Length: 100

{"error":"Key: 'CreateRequestParam.Task' Error:Field validation for 'Task' failed on the 'max' tag"
```

続いて、usecase 層では、handler から task を受け取り、 DB へデータの登録を行います
```go:usecase/todo.go
func (t *todo) Create(task string) error {
	// 登録するための構造体を作成
    todo := model.NewTodo(task)
    // DB にデータを登録
	if err := t.todoRepository.Create(todo); err != nil {
		return err
	}
	return nil
}
```
`todo := model.NewTodo(task)`では、 `domain/model/todo.go`の`NewTodo(task string)`を呼び出し、DB にデータを登録するための構造体を作成します

実装はこんな感じ
```go:domain/model/todo.go
type Todo struct {
	ID        int `gorm:"primaryKey"`
	Task      string
	Status    TaskStatus
	CreatedAt time.Time `gorm:"<-:false"`
	UpdatedAt time.Time `gorm:"<-:false"`
}

func NewTodo(task string) *Todo {
	return &Todo{
		Task:   task,
		Status: Created,
	}
}

// Task Status の独自型の定義
type TaskStatus string

const (
	Created    = TaskStatus("created")
	Processing = TaskStatus("processing")
	Done       = TaskStatus("done")
)
```
`NewTodo(task string)` 関数は Todo の構造体を作成して返します
初回作成なので、`Status` は新規作成を表す`Created（created`)を固定で入れます
また、 `Status`には事前に定義した値のみを扱うために、以下のように`type TaskStatus string` で独自型を定義しています
```go
// Task Status の独自型の定義
type TaskStatus string

const (
	Created    = TaskStatus("created")
	Processing = TaskStatus("processing")
	Done       = TaskStatus("done")
)
```
Todo の構造体は DB のテーブル 定義と合わせるようにしています
※本来レイヤ構造において、domain が他のレイヤに依存するような実装はだめなんだけど、今回はサンプルなので。。。
```go
type Todo struct {
	ID        int `gorm:"primaryKey"`
	Task      string
	Status    TaskStatus
	CreatedAt time.Time `gorm:"<-:false"`
	UpdatedAt time.Time `gorm:"<-:false"`
}
```
`gorm:xxx`　は gorm の用のタグで、テーブルの主キーの指定（`gorm:"primaryKey"`）や、指定したカラムを書き込まないように（`gorm:"<-:false"`）することができます

今回、`created_at`と`updated_at`は、テーブル定義として自動的に値が入るようにしているので、アプリケーション側からは書き込まないようにしています

テーブル定義はこんな感じ
```sql:migrations/0001_create_tables.sql
CREATE TABLE `todo` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT comment 'ID',
    `task` VARCHAR (128) NOT NULL comment 'タスク',
    `status` VARCHAR(20) NOT NULL comment 'タスクステータス',
    `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  comment '作成日時',
    `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment '更新日時',
PRIMARY KEY(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

最後に DB を操作する部分
`t.todoRepository.Create(todo)`では、repository を介して、`infrastructure/todo.go`の`Create(t *model.Todo)`を呼び出し、DB へのデータの登録を行います
```go:infrastructure/todo.go
func (td *Todo) Create(t *model.Todo) error {
	if err := td.db.Create(t).Error; err != nil {
		return err
	}
	return nil
}
```
`td.db.Create(t)`では、 gorm の `Create(value interface{})`を呼び出しデータの登録を行っています

gorm には 基本的な CURD 操作の為の関数が用意されているので用途によって使い分けます
以下は一例
| 操作  | 関数 |
| ------------- | ------------- |
| 新規作成（Create）  | Create  |
| 参照（Read）  | Take, Find  |
| 更新（Update）  | Update |
| 削除（Delete）  | Delete  |

※Update は 正確には upsert になります
主キーに該当するデータが有れば update、なければ insert
※参照用の関数に関しても、1件のみ取得であれば `Take`、複数件取得したい場合は `Find`を使うなど用途によって使い分けます

さて、これで登録API の実装完了したので、実際に API をコールしてデータが登録されるか見ていきたいと思います
まずは テーブル にデータが入っていないことを確認
```sql
mysql> select * from todo;
Empty set (0.00 sec)
```
はい、まだ空っぽです
それでは実際に API をコールしてデータの登録を行います
```zsh
% curl -i localhost/todo -H "Content-Type: application/json" -X POST -d '{"task": "test"}'
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Mon, 22 May 2023 16:07:23 GMT
Content-Length: 4
```
成功したっぽい
テーブルにデータが入っているか確認します
```sql
mysql> select * from todo;
+----+------+---------+---------------------+---------------------+
| id | task | status  | created_at          | updated_at          |
+----+------+---------+---------------------+---------------------+
|  1 | test | created | 2023-05-23 01:07:23 | 2023-05-23 01:07:23 |
+----+------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
```
ちゃんとデータが登録されてます。よかった
これで登録API は完成です

## 更新API の実装
続いて、更新API の実装です
更新API では、 id を指定して対象データの task と status の更新を行うことができます
以下は更新API の handler の実装です
```go:go:handler/todo.go
// パス(todo/:id)に指定されたパラメータを格納するための構造体
type UpdateRequestPathParam struct {
	ID int `uri:"id"`
}
// リクエストデータ(body)を格納するための構造体
type UpdateRequestBodyParam struct {
	Task   string           `json:"task" binding:"required,max=60"`
	Status model.TaskStatus `json:"status" binding:"required,task_status"`
}

func (t *todoHandler) Update(c *gin.Context) {
	var pathParam UpdateRequestPathParam
	var bodyParam UpdateRequestBodyParam
    // パスパラメータを構造体（UpdateRequestPathParam）にマッピング
	if err := c.ShouldBindUri(&pathParam); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
    // リクエストパラメータ(body)を構造体（UpdateRequestBodyParam）にマッピング
	if err := c.ShouldBindJSON(&bodyParam); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
    // usecase の呼び出し
	if err := t.usecase.Update(pathParam.ID, bodyParam.Task, bodyParam.Status); err != nil {
		c.JSON(http.StatusInternalServerError, "")
		return
	}
    // レスポンスを返却
	c.JSON(http.StatusNoContent, nil)
}
```
`UpdateRequestPathParam` と `UpdateRequestBodyParam`でそれぞれパスパラメータとボディに指定されたリクエストパラメータを受け取る構造体を定義しています
`UpdateRequestBodyParam`では、登録API と同様にバリデーション設定を行っているのですが、ここでは`task_status`というカスタムバリデータを作り独自のバリデーションチェックを行うようにしています
これは、更新用API ではステータスの更新もできるのですが、事前定義されたステータスの値以外を受付ないようにするためです

カスタムバリデータの実装は以下のようになっています
```go:handler/validator/validator.go
package validator

import (
	"app/domain/model"

	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
)

func SetupValidator() error {
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		if err := v.RegisterValidation("task_status", ValidateTaskStatus); err != nil {
			return err
		}
	}
	return nil
}
func ValidateTaskStatus(fl validator.FieldLevel) bool {
	return model.TaskStatusMap[model.TaskStatus(fl.Field().String())]
}
```
`ValidateTaskStatus(fl validator.FieldLevel)`が実際のバリデーションチェックを行う部分になっており、入力された値が事前定義された値に該当するかをチェックをし、 結果を bool で返します
チェックには、`domain/model/todo.go`に定義してある以下の関数を使用しています
```go
var TaskStatusMap = map[TaskStatus]bool{
	Created:    true,
	Processing: true,
	Done:       true,
}
```
入力された値が `TaskStatusMap`に存在すれば `true`を返し、存在しなければ `false`を返します

`SetupValidator()`では作成したバリデーションチェックの関数の登録を行います
```go
func SetupValidator() error {
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        // 第一引数に任意のタグ名を指定、第二引数にバリデーションチェックに使用する関数を指定
		if err := v.RegisterValidation("task_status", ValidateTaskStatus); err != nil {
			return err
		}
	}
	return nil
}
func ValidateTaskStatus(fl validator.FieldLevel) bool {
	return model.TaskStatusMap[model.TaskStatus(fl.Field().String())]
}
```
続いて usecase 層
こちらも登録API　と同様に handler から task の情報を受け取りデータの更新を行います
```go:usecase/todo.go
func (t *todo) Update(id int, task string, status model.TaskStatus) error {
    // 更新用の構造体を生成
	todo := model.NewUpdateTodo(id, task, status)
    // DBのデータの更新
	if err := t.todoRepository.Update(todo); err != nil {
		return err
	}
	return nil
}
```
`todo := model.NewUpdateTodo(id, task ,status)`では、 `domain/model/todo.go`の`NewUpdateTodo(id int, task string, status TaskStatus)`を呼び出し、DB にデータを登録するための構造体を作成します
実装はこんな感じ
```go:domain/model/todo.go
func NewUpdateTodo(id int, task string, status TaskStatus) *Todo {
	return &Todo{
		ID:     id,
		Task:   task,
		Status: status,
	}
}
```
続いて DB 操作
登録時と同様に `t.todoRepository.Update(todo)`で repository を介して
`infrastructure/todo.go`の`Update(t *model.Todo)を呼び出し、DB へのデータの更新を行います
```go:infrastructure/todo.go
func (td *Todo) Update(t *model.Todo) error {
	if err := td.db.Save(t).Error; err != nil {
		return err
	}
	return nil
}
```
これで更新API の実装が完了したので、実際に　API をコールしてデータの更新をしてみたいと思います
先程作成した以下のデータをのステータスを更新します
```sql
mysql> select * from todo where id = 1;
+----+------+---------+---------------------+---------------------+
| id | task | status  | created_at          | updated_at          |
+----+------+---------+---------------------+---------------------+
|  1 | test | created | 2023-05-18 02:55:10 | 2023-05-18 02:55:10 |
+----+------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
```
実際に API をコールして、タスクステータスを created -> processing に更新します。
```zsh
% curl -i localhost/todo/1 -H "Content-Type: application/json" -X PUT -d '{"task": "test", "status": "processing"}'
HTTP/1.1 204 No Content
Content-Type: application/json; charset=utf-8
Date: Mon, 22 May 2023 16:08:36 GMT
```

データを確認します
```sql
mysql> select * from todo where id = 1;
+----+------+------------+---------------------+---------------------+
| id | task | status     | created_at          | updated_at          |
+----+------+------------+---------------------+---------------------+
|  1 | test | processing | 2023-05-23 01:07:23 | 2023-05-23 01:08:36 |
+----+------+------------+---------------------+---------------------+
1 row in set (0.00 sec)
```
ちゃんと更新されてますね。よかった
（今となってはステータスだけ更新するAPI にすればよかったな。。。）
これで、更新API　は完成です

## 取得API の実装
取得API では、 パスパラメータに設定した id に該当するデータを1件取得して返します
この部分
`/todo/{id}`

以下は 取得API の handler の実装です
```go:handler/todo.go
// パス(todo/:id)に指定されたパラメータを格納するための構造体
type FindRequestParam struct {
	ID int `uri:"id" binding:"required"`
}

func (t *todoHandler) Find(c *gin.Context) {
	var req FindRequestParam
    // パスパラメータを構造体（FindRequestParam）にマッピング
    
	if err := c.ShouldBindUri(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
    // usecase の呼び出し
	res, err := t.usecase.Find(req.ID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if res == nil {
        // 検索結果(res = nil）が存在しなかった場合は 404 not found を返す
		c.JSON(http.StatusNotFound, nil)
		return
	}
    // レスポンスを返却
	c.JSON(http.StatusOK, res)
}
```
続いて、usecase 層
usecase 層では、 handler から受け取った id 元に検索を行い結果を返します
```go:usecase/todo.go
func (t *todo) Find(id int) (*model.Todo, error) {
	todo, err := t.todoRepository.Find(id)
	if err != nil {
		return nil, err
	}
	return todo, nil
}
```
infrastructure 層の実装は以下です
```go:infrastructure/todo.go
func (td *Todo) Find(id int) (*model.Todo, error) {
	var todo *model.Todo
	err := td.db.Where("id = ?", id).Take(&todo).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil
		}
		return nil, err
	}
	return todo, nil
}
```
`Take` 関数を使った時、レコードが存在しないと`gorm.ErrRecordNotFound`というエラーが返ってきます。レコードが存在しない場合にシステムエラーのような扱いをしたくなく、 404 でレスポンスを返したいので、ここでは `err !=nil`じゃなかった場合、エラーの種類をチェックして エラーが`gorm.ErrRecordNotFound`だった場合には nil を返すようにしています

```go
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil
		}
        ...
```
これで、取得API の実装が完了したので実際に　API をコールしてデータが取得できるか確認してみたいと思います
先程利用した以下のデータを取得します
```sql
mysql> select * from todo where id = 1;
+----+------+------------+---------------------+---------------------+
| id | task | status     | created_at          | updated_at          |
+----+------+------------+---------------------+---------------------+
|  1 | test | processing | 2023-05-18 02:55:10 | 2023-05-19 01:55:33 |
+----+------+------------+---------------------+---------------------+
```
API をコールします
```zsh
% curl -i -XGET localhost/todo/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Mon, 22 May 2023 16:10:00 GMT
Content-Length: 114

{"ID":1,"Task":"test","Status":"processing","CreatedAt":"2023-05-23T01:07:23Z","UpdatedAt":"2023-05-23T01:08:36Z"}
```
ちゃんとデータが取得できますね。よかった

## 全件取得API の実装
続いて全件取得API の実装です
これは、取得API と違い、 id の指定をせずに、現在　DB に登録されている task を全件取得して返します
※実際に使うときは limit で制限かけるとかした方がいいです

以下は 全件取得API の handler の実装です
```go:handler/todo.go
func (t *todoHandler) FindAll(c *gin.Context) {
	res, err := t.usecase.FindAll()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, res)
}
```
特に目新しいところはないですね
続いて、 usecase です
```go:usecase/todo.go
func (t *todo) FindAll() ([]*model.Todo, error) {
	todo, err := t.todoRepository.FindAll()
	if err != nil {
		return nil, err
	}
	return todo, nil
}

```
infrastructure 
```go:infrastructure/todo.go
func (td *Todo) FindAll() ([]*model.Todo, error) {
	var todos []*model.Todo
	err := td.db.Find(&todos).Error
	if err != nil {
		return nil, err
	}
	return todos, nil
}
```
gorm の Find 関数では結果が配列で返ってくるので、検索結果を格納するための構造体も配列として定義しています
```go
	var todos []*model.Todo
	err := td.db.Find(&todos).Error
	if err != nil {
		return nil, err
	}
```
これで、全件取得API の実装が完了したので、実際に API をコールしてデータを取得してみたいと思います
今はデータが１件しか登録されていないので、事前にもう１件登録しておきます
せっかくなので、API で登録しましょう
```bash
% curl -i localhost/todo -H "Content-Type: application/json" -X POST -d '{"task": "test2"}'
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Mon, 22 May 2023 16:10:30 GMT
Content-Length: 4
```
実際にデータが登録されたか確認します
```sql
mysql> select * from todo;
+----+-------+------------+---------------------+---------------------+
| id | task  | status     | created_at          | updated_at          |
+----+-------+------------+---------------------+---------------------+
|  1 | test  | processing | 2023-05-23 01:07:23 | 2023-05-23 01:08:36 |
|  2 | test2 | created    | 2023-05-23 01:10:30 | 2023-05-23 01:10:30 |
+----+-------+------------+---------------------+---------------------+
2 rows in set (0.00 sec)
```
ちゃんと登録されていますね
では、全件取得API　でこの２件のデータが期待通り取得できるか試してみます
```zsh
% curl -i -XGET localhost/todo
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Mon, 22 May 2023 16:11:19 GMT
Content-Length: 229

[{"ID":1,"Task":"test","Status":"processing","CreatedAt":"2023-05-23T01:07:23Z","UpdatedAt":"2023-05-23T01:08:36Z"},{"ID":2,"Task":"test2","Status":"created","CreatedAt":"2023-05-23T01:10:30Z","UpdatedAt":"2023-05-23T01:10:30Z"}]
```
ちゃんと２件、データが取得できてますね。良かった
見やすいように整形するとこんな感じです
```json
[
	{
		"ID": 1,
		"Task": "test",
		"Status": "processing",
		"CreatedAt": "2023-05-23T01:07:23Z",
		"UpdatedAt": "2023-05-23T01:08:36Z"
	},
	{
		"ID": 2,
		"Task": "test2",
		"Status": "created",
		"CreatedAt": "2023-05-23T01:10:30Z",
		"UpdatedAt": "2023-05-23T01:10:30Z"
	}
]
```

## 削除API の実装
最後に、削除API の実装です

削除API は id を指定して対象データの削除を行います
以下は削除API の handler の実装です
```go:handler/todo.go

// パス(todo/:id)に指定されたパラメータを格納するための構造体
type DeleteRequestParam struct {
	ID int `uri:"id"`
}

func (t *todoHandler) Delete(c *gin.Context) {
	var req DeleteRequestParam
    // パスパラメータを構造体（DeleteRequestParam）にマッピング
	if err := c.ShouldBindUri(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
    // usecase の呼び出し
	if err := t.usecase.Delete(req.ID); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
    // レスポンスを返却
	c.JSON(http.StatusNoContent, nil)
}
```
続いて usecase 
```go:usecase/todo.go
func (t *todo) Delete(id int) error {
	if err := t.todoRepository.Delete(id); err != nil {
		return err
	}
	return nil
}
```
infrastructure 
```go:infrastructure/todo.go
func (td *Todo) Delete(id int) error {
	if err := td.db.Where("id = ?", id).Delete(&model.Todo{}).Error; err != nil {
		return err
	}
	return nil
}
```
gorm の Delete 関数を使いデータの削除を行っています

これで、削除API の実装が完了したので、実際にデータを削除してみます
以下のデータの削除を行います
```sql
mysql> select * from todo where id = 2;
+----+-------+---------+---------------------+---------------------+
| id | task  | status  | created_at          | updated_at          |
+----+-------+---------+---------------------+---------------------+
|  2 | test2 | created | 2023-05-23 01:10:30 | 2023-05-23 01:10:30 |
+----+-------+---------+---------------------+---------------------+
1 row in set (0.00 sec)

```
削除API の実行
```zsh
% curl -i localhost/todo/2 -X DELETE
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Mon, 22 May 2023 16:12:41 GMT
Content-Length: 4
```
いけたっぽい。実際にデータが削除されたか確認します
```sql
mysql> select * from todo where id = 2;
Empty set (0.00 sec)
```
ちゃんと削除されてますね。よかった
以上で、API の実装は完了です

# 最後に
簡単ですが、基本的な CRUD 操作を行うための REST API を実装しました
今回作ったものは以下のリポジトリに配置しています
一応テストコードも書いているので、興味のある方は見てみてください

https://github.com/Ixy-194/go-api-sample-todo

次回は githubactions　を使って CI の構築でもしてみようと思います
ではでは。