Go × GinでRESTfulなAPIを構築！新聞管理のコントローラー実装を徹底解説

## 概要

Go言語とGinフレームワークを活用して、RESTful APIを効率的に構築する方法を解説します。この記事では、新聞を管理するAPIを例に、コントローラー層の設計・実装を学びます。

この記事を読めば以下のことがわかります：

- **Ginフレームワークの基本的な使い方**
- **RESTfulなエンドポイントの実装方法**
- **HTTPステータスコードやリクエスト/レスポンスの正しい扱い方**
- **ハンドラーにおけるエラー処理のポイント**

## コントローラーの役割とは？

コントローラーは、クライアントからのリクエストを受け取り、適切なレスポンスを返すためのロジックを記述する層です。以下の役割を果たします：

1. **リクエストの検証**  
   リクエストボディやURLパラメータの内容が正しいかをチェックします。

2. **ビジネスロジックの呼び出し**  
   モデル層やサービス層を呼び出して、データベース操作などの処理を委譲します。

3. **レスポンスの整形**  
   クライアントが期待する形式でデータを返します。

## 新聞管理APIのコントローラー実装

以下のコードは、新聞を管理するコントローラー層の実装例です。

### 全体の構造

```go
package controllers

import (
	"net/http"

	"github.com/gin-gonic/gin"

	"go-api-newspaper/api"
	"go-api-newspaper/app/models"
	"go-api-newspaper/pkg/logger"
)

// メソッドを関連付けることで、各エンドポイントの処理を実装。
type NewspaperHandler struct{}
```

`NewspaperHandler` は新聞に関連するエンドポイントの処理を集約した構造体です。この構造体にメソッドを定義し、各APIエンドポイントと紐付けます。

## 各エンドポイントの実装

### 新聞の作成（Create）

```go
func (a *NewspaperHandler) CreateNewspaper(c *gin.Context) {
	var requestBody api.CreateNewspaperJSONRequestBody // JSONリクエストボディを受け取る構造体
	if err := c.ShouldBindJSON(&requestBody); err != nil {
		logger.Warn(err.Error())
		c.JSON(http.StatusBadRequest, api.ErrorResponse{Message: err.Error()})
		return
	}

	createdNewspaper, err := models.CreateNewspaper(
		requestBody.Title,
		requestBody.ColumnName)
	if err != nil {
		logger.Error(err.Error())
		c.JSON(http.StatusInternalServerError, api.ErrorResponse{Message: err.Error()})
		return
	}

	c.JSON(http.StatusCreated, createdNewspaper) // 201ステータスで作成結果を返す
}
```

**解説**:
- `c.ShouldBindJSON` を使って、リクエストボディを構造体にマッピングします。
- モデル層の `CreateNewspaper` メソッドを呼び出してデータを保存します。
- 成功時は `http.StatusCreated`（201ステータス）を返します。


### 新聞の取得（Read）

```go
func (a *NewspaperHandler) GetNewspaperById(c *gin.Context, ID int) {
	newspaper, err := models.GetNewspaper(ID)
	if err != nil {
		logger.Error(err.Error())
		c.JSON(http.StatusInternalServerError, api.ErrorResponse{Message: err.Error()})
		return
	}

	c.JSON(http.StatusOK, newspaper) // 200ステータスでデータを返す
}
```

**解説**:
- `models.GetNewspaper` を呼び出し、指定されたIDの新聞データを取得します。
- データが正常に取得できた場合は、`http.StatusOK`（200ステータス）を返します。
- エラーが発生した場合は、`http.StatusInternalServerError` を返してエラー内容をレスポンスします。

---

### 新聞の更新（Update）

```go
func (a *NewspaperHandler) UpdateNewspaperById(c *gin.Context, ID int) {
	var requestBody api.UpdateNewspaperByIdJSONRequestBody
	if err := c.ShouldBindJSON(&requestBody); err != nil {
		logger.Warn(err.Error())
		c.JSON(http.StatusBadRequest, api.ErrorResponse{Message: err.Error()})
		return
	}

	newspaper, err := models.GetNewspaper(ID)
	if err != nil {
		logger.Error(err.Error())
		c.JSON(http.StatusInternalServerError, api.ErrorResponse{Message: err.Error()})
		return
	}

	if requestBody.Title != nil {
		newspaper.Title = *requestBody.Title
	}
	if requestBody.ColumnName != nil {
		newspaper.ColumnName = *requestBody.ColumnName
	}

	if err := newspaper.Save(); err != nil {
		logger.Error(err.Error())
		c.JSON(http.StatusInternalServerError, api.ErrorResponse{Message: err.Error()})
		return
	}

	c.JSON(http.StatusOK, newspaper) // 200ステータスで更新結果を返す
}
```

**解説**:
- `c.ShouldBindJSON` でリクエストボディをパースし、必要に応じてモデルのプロパティを更新します。
- モデルの `Save` メソッドを利用して変更を保存します。


### 新聞の削除（Delete）

```go
func (a *NewspaperHandler) DeleteNewspaperById(c *gin.Context, ID int) {
	newspaper := models.Newspaper{ID: ID}

	if err := newspaper.Delete(); err != nil {
		logger.Error(err.Error())
		c.JSON(http.StatusInternalServerError, api.ErrorResponse{Message: err.Error()})
		return
	}

	c.JSON(http.StatusNoContent, nil) // 204ステータスで成功を通知
}
```

**解説**:
- モデルの `Delete` メソッドを呼び出して対象データを削除します。
- 削除が成功した場合は、`http.StatusNoContent`（204ステータス）を返します。


## まとめ

この記事では、Ginを使ったRESTful APIのコントローラー実装について学びました。以下のポイントを押さえましょう：

1. **Ginの `Context` を使ったリクエスト・レスポンスの管理**
2. **HTTPステータスコードを適切に使用してクライアントに状態を通知**
3. **モデル層と連携し、ビジネスロジックをシンプルに実装**

この記事のコードを活用して、あなたのプロジェクトで効率的なAPI設計を実現してください！