初心者向け！GoでAPIサーバーを作成する手順を徹底解説 ～Docker連携でMySQLを操作する実装～

## はじめに

APIサーバーを構築する際、Go（Golang）とGinフレームワークを使うのは非常に効率的です。この記事では、コードを細かく分解し、初めての方でもわかりやすいように徹底的に解説します。また、Dockerで立ち上げたMySQLと接続する部分も含めて、環境構築から動作確認までを網羅します。

## 前提条件

- **Goの基本的な知識**（簡単な構文を理解しているレベルでOK）
- **Docker環境のセットアップ済み**
- **MySQLをDockerで動かしている**


## 実装するコードの全体像

以下は、APIサーバーを実装するコード全体です。詳細な説明は後述します。

```go
package main

import (
	"context"       // コンテキスト処理を提供する標準パッケージ
	"encoding/json" // JSON処理をサポート
	"errors"        // エラー処理の標準パッケージ
	"fmt"           // フォーマットされたI/Oを提供
	"go-api-newspaper/pkg/logger" // ロガー（ログ出力用のパッケージ）
	"log"           // ログ出力用の標準パッケージ
	"net/http"      // HTTPサーバーの実装に必要な標準パッケージ
	"os"            // OS操作用（環境変数やシグナル処理）
	"os/signal"     // OSシグナル処理を提供
	"syscall"       // システムコールシグナルを提供
	"time"          // 時間操作をサポートする標準パッケージ

	"github.com/gin-gonic/gin" // Gin Webフレームワーク
	middleware "github.com/oapi-codegen/gin-middleware" // OpenAPIリクエストバリデータ
	swaggerfiles "github.com/swaggo/files" // Swagger UIファイル
	ginSwagger "github.com/swaggo/gin-swagger" // Swagger UIをGinでラップ
	"github.com/swaggo/swag" // Swaggerの機能を提供

	"go-api-newspaper/api"             // 自作APIパッケージ
	"go-api-newspaper/app/controllers" // 自作コントローラパッケージ
	"go-api-newspaper/app/models"      // 自作モデルパッケージ
	"go-api-newspaper/configs"         // 自作設定パッケージ
)

func main() {
	// データベースの初期化
	if err := models.SetDatabase(models.InstanceMySQL); err != nil {
		// 初期化に失敗した場合、ログにエラーを記録してプログラムを終了
		logger.Fatal(err.Error())
	}

	// Ginのデフォルト設定を使用してルーターを作成
	router := gin.Default()

	// OpenAPI仕様を取得（API仕様のバリデーション用）
	swagger, err := api.GetSwagger()
	if err != nil {
		// 取得に失敗した場合、プログラムを終了
		panic(err)
	}

	// 開発環境の場合、Swagger UIを有効化
	if configs.Config.IsDevelopment() {
		swaggerJson, _ := json.Marshal(swagger) // OpenAPI仕様をJSON形式に変換
		var SwaggerInfo = &swag.Spec{
			InfoInstanceName: "swagger",
			SwaggerTemplate:  string(swaggerJson),
		}
		swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) // Swagger情報を登録
		router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) // Swagger UIエンドポイントを追加
	}

	// APIのエンドポイントを設定
	apiGroup := router.Group("/api") // ベースURLを`/api`に設定
	{
		v1 := apiGroup.Group("/v1") // バージョン1のAPIをグループ化
		{
			// OpenAPI仕様に基づくリクエストバリデーションをミドルウェアとして追加
			v1.Use(middleware.OapiRequestValidator(swagger))
			newspaperHandler := &controllers.NewspaperHandler{} // コントローラをインスタンス化
			api.RegisterHandlers(v1, newspaperHandler)          // ハンドラをエンドポイントに登録
		}
	}

	// HTTPサーバーの設定
	srv := &http.Server{
		Addr:    "0.0.0.0:8080", // サーバーをポート8080で待機
		Handler: router,         // Ginルーターをハンドラとして設定
	}

	// サーバーをバックグラウンドで起動
	go func() {
		if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			// サーバーが予期せず停止した場合、エラーログを出力
			logger.Fatal(err.Error())
		}
	}()

	// OSシグナルを受け取るためのチャネルを作成
	quit := make(chan os.Signal, 1)
	// シグナルをキャッチ（SIGINT: Ctrl+C, SIGTERM: 終了要求）
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit // シグナルを受け取るまでブロック

	// サーバーのシャットダウン処理
	log.Println("Shutdown Server ...")
	defer logger.Sync() // ログのバッファをフラッシュ

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // 2秒のタイムアウト付きコンテキスト
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		// シャットダウンに失敗した場合、エラーログを出力
		logger.Error(fmt.Sprintf("Server Shutdown: %s", err.Error()))
	}
	<-ctx.Done() // コンテキストの完了を待つ
	logger.Info("Shutdown") // シャットダウン完了をログ出力
}
```

## 詳細解説

### データベースの初期化
```go
if err := models.SetDatabase(models.InstanceMySQL); err != nil {
	// 初期化に失敗した場合、ログにエラーを記録してプログラムを終了
	logger.Fatal(err.Error())
}
```
`models.SetDatabase(models.InstanceMySQL)`を呼び出してデータベース接続を設定します。 Dockerで立ち上げたMySQLコンテナと接続するための設定が行われます。

### Ginのデフォルト設定を使用してルーターを作成
```go
router := gin.Default()
```
- **Ginとは？**
Golangで人気のあるWebフレームワークで、高速なAPIの構築に利用されます。

- **gin.Default()とは？**
Ginのデフォルト設定を使ってルーターを作成します。この設定には以下が含まれます：
　・Loggerミドルウェア: リクエストをログに記録する。
　・Recoveryミドルウェア: パニックをキャッチしてサーバーを落とさずにエラーレスポンスを返す。
- **カスタム設定にしたい場合はどうする？**
``gin.New()``を使用して、必要なミドルウェアを自分で追加することができます。

### Swaggerのセットアップ
```go
// OpenAPI仕様を取得（API仕様のバリデーション用）
	swagger, err := api.GetSwagger()
	if err != nil {
		panic(err)
	}

	// 開発環境の場合、Swagger UIを有効化
	if configs.Config.IsDevelopment() {
		swaggerJson, _ := json.Marshal(swagger) // OpenAPI仕様をJSON形式に変換
		var SwaggerInfo = &swag.Spec{
			InfoInstanceName: "swagger",
			SwaggerTemplate:  string(swaggerJson),
		}
		swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) // Swagger情報を登録
		router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) // Swagger UIエンドポイントを追加
	}
```
- **Swaggerとは？**
API仕様を記述するためのフォーマット（OpenAPI Specification）です。Swaggerを使うと、以下が可能になります：
  ・APIドキュメントの自動生成。
  ・Swagger UIを使ったAPIのインタラクティブなテスト。

- **swag.Spec とは？**
``swag.Spec``はSwagger仕様を表現する構造体です。
``InfoInstanceName``は登録するSwaggerの名前を指定し、``SwaggerTemplate``はAPI仕様をJSON形式で指定します。

- ``swag.Register``**とは？**
Swagger仕様を内部に登録します。この後、Ginのルート設定などでSwagger UIやAPI仕様を利用可能にします。

- ``ginSwagger.WrapHandler``**とは？**
Swagger UIを提供するためのハンドラを作成します。これにより、``/swagger/*any``にアクセスするとSwagger UIが表示されます。


### エンドポイントの登録

```go
apiGroup := router.Group("/api")
{
	v1 := apiGroup.Group("/v1")
	{
		// OpenAPI仕様に基づくリクエストバリデーションをミドルウェアとして追加
		v1.Use(middleware.OapiRequestValidator(swagger)) // 変数swaggerのAPI仕様に基づくバリデーション
		newspaperHandler := &controllers.NewspaperHandler{}
		api.RegisterHandlers(v1, newspaperHandler) // ルーターに登録
	}
}
```


Ginフレームワークを使い、`/api/v1`にエンドポイントを設定。  
OpenAPI仕様に基づきリクエストをバリデーションします。

#### v1.Use(middleware.OapiRequestValidator(swagger))
- ``OapiRequestValidator``**とは？**
OpenAPI仕様に基づいてリクエストをバリデーションするミドルウェアです。

- **何をバリデーションするのか？**
・リクエストのパスやメソッドが仕様と一致しているか。
・リクエストのパラメータやボディが仕様に準拠しているか。

これにより、不正なリクエストがAPIサーバーに到達するのを防ぎます。

#### api.RegisterHandlers(v1, newspaperHandler)とは？
OpenAPIで定義したエンドポイントとハンドラをルーターに登録します。これにより、指定されたエンドポイントにリクエストが来たときに、対応するハンドラが実行されます。


### サーバーの起動

```go
srv := &http.Server{
	Addr:    "0.0.0.0:8080",
	Handler: router,
}
```
- **http.Serverとは？**
Goの標準ライブラリで提供されるHTTPサーバーの設定構造体です。

```go
go func() { // ListenAndServe　サーバーを起動しリクエストをまつ
	if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
		logger.Fatal(err.Error())
	}
}()
```
- **srv.ListenAndServe()とは？**
サーバーを指定したアドレスとポートで起動します。

- **errors.Isとは？**
エラーが特定の型や内容に一致しているかを判定します。

- **http.ErrServerClosedとは？**
サーバーが正常にシャットダウンされたことを示すエラーです。このエラー以外の場合に致命的なログを出力します。

別ゴルーチンで実行され、メインの実行フローに影響を与えません。

### チャンネルでシグナルをキャッチ

```go
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutdown Server ...")
```

- **チャンネルとは？**
Goの並行処理で、ゴルーチン間の通信を行うためのデータ構造です。

- **なぜこれをするのか？**
システムのシグナル（例：Ctrl+C）をキャッチしてサーバーを優雅にシャットダウンします。

### サーバーのシャットダウン開始
```go
log.Println("Shutdown Server ...")
defer logger.Sync() // ログのバッファをフラッシュする
```

- シグナルを受信してサーバーのシャットダウン処理を開始したことをログに出力します。これはすぐに実行されます。
- ``logger.Sync()``は、バッファ内に溜まったログを出力するための処理です。

### タイムアウト付きのコンテキストを作成
```go
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // ２秒のタイムアウトを持つコンテキスト
defer cancel()
```
- ``context.WithTimeout(context.Background(), 2*time.Second)``**とは?**
2秒のタイムアウトを持つコンテキストを作成します。
サーバーのシャットダウン処理が完了しない場合でも、2秒後には強制的に処理を終了させる仕組みを提供します。

- ``defer cancel()``**とは？**
コンテキストのキャンセル関数を登録し、プログラム終了時にリソースを解放します。この時点では実行されません。

### サーバーのシャットダウン

```go
if err := srv.Shutdown(ctx); err != nil {
    logger.Error(fmt.Sprintf("Server Shutdown: %s", err.Error()))
}
```
- ``srv.Shutdown(ctx)``**とは？**
サーバーを優雅にシャットダウンします。
未処理のリクエストを完了させる時間を確保します。
タイムアウト（2秒）が経過した場合、未完了のリクエストを切り捨てて終了します。

- **エラーチェック**
シャットダウン中にエラーが発生した場合、ログにエラーメッセージを出力します。

### コンテキストの終了を待つ
```go
<-ctx.Done()
logger.Info("Shutdown")
```
- コンテキストが終了（タイムアウトまたは処理完了）するまで待機します。
``srv.Shutdown(ctx)``が成功すると、``ctx.Done()``が閉じられます。
タイムアウトが発生した場合も同様に閉じられます。


## まとめ

このコードは、APIサーバー構築の基本をしっかりと押さえています。  
DockerでのMySQL連携やSwaggerによるAPI仕様の可視化、シグナル対応による優雅なシャットダウンなど、実用的な要素が含まれています。

ぜひ、このコードをコピーして、実際に動かしてみてください！  
APIサーバーの作成がぐっと身近になります。