【速習シリーズ ①】Go言語でREST APIを作ろう!!
1. 本記事の意図
こんにちは、どすこいです!
最近Go言語を勉強し始めまして、output用にこの記事を書きました!
今回のシリーズでは、Go言語を使用して簡単なREST APIを構築する方法を紹介します。
Go言語はそのパフォーマンスの高さと並行処理の容易さで知られており、Webサービスの開発に適しています。
この記事を通じて、Go言語の基本的なWeb開発技術を学び、実際に動作するAPIを構築することができます。
対象者
- GO言語の基礎文法を学んだ方
- 簡単なWeb APIを作ってアウトプットしたい方
2. 使用技術: Go, Gin, Gorm, PostgreSQL
このプロジェクトでは以下の技術を使用します:
- Go言語: 高性能なコンパイル言語で、Webサーバーの開発に適しています。
- Gin: Go言語用の高速なHTTP Webフレームワークで、ルーティングやミドルウェアのサポートを提供します。
- Gorm: Go言語のオブジェクト関係マッピング(ORM)ライブラリで、データベース操作を簡単にします。
- PostgreSQL: オープンソースの関係データベースシステムで、信頼性と拡張性が高いです。
3. 開発
3.1 環境構築
まず、Go言語、PostgreSQL、および必要なライブラリをインストールします。
GinとGormはGoのパッケージマネージャを使用してインストールできます。
Ginのインストール
go get -u github.com/gin-gonic/gin
次にGormのインストールです。
Gormを使用するには、対応するデータベースドライバもインストールする必要があります。例えば、PostgreSQLを使用する場合は、以下のコマンドでPostgreSQLドライバをインストールします。
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
3.2 プロジェクトの構造
.
├── Makefile
├── README.md
├── docker-compose.yml
├── go.mod
├── go.sum
└── main.go
3.3 データベースの設定
まずは、dockerでPosgresqlのコンテナを作成します。
version: '3'
services:
  db:
    image: postgres:14
    container_name: postgres
    ports:
      - 5432:5432
    volumes:
      - db-store:/var/lib/postgresql/data
      - ./script:/docker-entrypoint-initdb.d
    environment:
      - POSTGRES_PASSWORD=password
volumes:
  db-store:
今回は、Goの解説をしたいので、dockerの解説は省略させていただきます。🙏
docker compose up -d
上記のコマンドを実行するとコンテナが立ち上がります。
ちなみに、今回はdbdiagramを使用して作成しました。

3.4 REST APIの構築
3.4.1 Hello Worldを作成
何はともあれ、まずはGinでHello WorldのAPIを作成しましょう!
package main
import (
	"github.com/gin-gonic/gin"
)
func main() {
	// Ginエンジンのインスタンスを作成
	r := gin.Default()
	// ルートURL ("/") に対するGETリクエストをハンドル
	r.GET("/", func(c *gin.Context) {
		// JSONレスポンスを返す
		c.JSON(200, gin.H{
			"message": "Hello World",
		})
	})
	// 8080ポートでサーバーを起動
	r.Run(":8080")
}
go run main.go
{
  "message": "Hello World"
}
このコードは、特定のHTTPエンドポイントに対するリクエストを処理し、JSON形式のレスポンスを返します。
- 
Ginエンジンのインスタンスを作成: r := gin.Default()ここで、 gin.Default()はGinフレームワークの新しいインスタンスを作成します。このインスタンスは、ルーティング、ミドルウェア、およびリクエスト処理の機能を提供します。
- 
ルートURLに対するGETリクエストのハンドリング: r.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "Hello World", }) })この部分では、ルートURL( "/")に対するGETリクエストをハンドルするルートを定義しています。リクエストが来ると、無名関数が実行され、ステータスコード200(HTTP OK)と共にJSON形式のレスポンスが返されます。このレスポンスには、{"message": "Hello World"}という内容が含まれています。
- 
サーバーの起動: r.Run(":8080")Runメソッドは、指定されたポート(この場合は8080)でWebサーバーを起動します。このサーバーは、上で定義したルートに対するリクエストを待ち受けます。
3.4.2 Gormと接続しよう!
Gormと接続していきます。main関数の中に以下のコードを書いてください。
// PostgreSQL接続情報
dsn := "host=localhost user=yourusername password=yourpassword dbname=yourdbname port=5432 sslmode=disable TimeZone=Asia/Tokyo"
// GORMでデータベースに接続
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
  log.Fatal("Failed to connect to database:", err)
}
このままでも良いですが、接続情報は秘密にしたいので、環境変数を取得しましょう!
以下のように変更してください。
POSTGRES_PASSWORD=password
POSTGRES_USER=postgres
POSTGRES_DB=postgres
package main
import (
  "fmt"
  "log"
  "os"
  "github.com/gin-gonic/gin"
  "github.com/joho/godotenv"
  "gorm.io/driver/postgres"
  "gorm.io/gorm"
)
func main() {
  // .envファイルから環境変数を読み込む
  err := godotenv.Load()
  if err != nil {
	log.Fatal("Error loading .env file")
  }
   // 環境変数から接続情報を取得
  dbUser := os.Getenv("POSTGRES_USER")
  dbPassword := os.Getenv("POSTGRES_PASSWORD")
  dbName := os.Getenv("POSTGRES_DB")
  dbHost := "localhost" // または環境変数から取得
  dbPort := "5432"      // または環境変数から取得
  // DSNを構築
  dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Tokyo", dbHost, dbUser, dbPassword, dbName, dbPort)
  // GORMでデータベースに接続
  db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
  if err != nil {
	log.Fatal("Failed to connect to database:", err)
  }
  // データベースにテーブルを作成
  db.AutoMigrate()
  // Ginエンジンのインスタンスを作成
  r := gin.Default()
  // ルートURL ("/") に対するGETリクエストをハンドル
  r.GET("/", func(c *gin.Context) {
       // JSONレスポンスを返す
       c.JSON(200, gin.H{
       "message": "Hello World",
     })
  })
  // 8080ポートでサーバーを起動
  r.Run(":8080")
}
上記のコードの説明です。
.envファイルから環境変数を読み込む:
godotenv.Load() は.envファイルを読み込み、その内容を現在のプロセスの環境変数として設定します。これにより、.envファイル内のキー=値のペアが環境変数として利用可能になります。
os.Getenvは、この関数は指定された環境変数の値を取得します。ここでは、PostgreSQLデータベースのユーザー名、パスワード、データベース名を取得しています。
続いて、Gormのdb.AutoMigrate()について説明します。
db.AutoMigrate() は、GORMを使用したGo言語のコードで見られる一般的な命令です。この命令は、データベースのスキーマを自動的に移行(更新)するために使用されます。
具体的には、以下のような機能を提供します:
テーブルの自動作成: 構造体に基づいて、対応するテーブルがデータベース内に存在しない場合、GORMはそのテーブルを自動的に作成します。
スキーマの自動更新: 既存のテーブルのスキーマ(構造)が Task 構造体の定義と異なる場合、GORMはテーブルのスキーマを自動的に更新します。これには、新しい列の追加や既存の列の型の変更などが含まれます。
安全なスキーマ変更: AutoMigrate は、データの損失を引き起こす可能性のある変更(列の削除やデータ型の変更など)を行いません。そのため、この機能は比較的安全に使用できます。
つまり、構造体を作成し、そこから、DBのスキーマを自動で生成してくれます。
今回は、以下のSQL文をGormの書き方に合うように合うよう書いていきます。
CREATE TABLE tasks (
  id SERIAL PRIMARY KEY,
  task VARCHAR(255), -- タスクの最大長を255文字に設定
  is_completed BOOLEAN, -- タスクが完了したかどうか
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, -- レコード作成時のタイムスタンプ
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -- レコード更新時のタイムスタンプ
);
3.4.3 構造体を書こう!
main.goの中に以下のように書いてください。
ただし、main関数の外に書いてください。
type Task struct {
  ID          uint           `gorm:"primary_key"`
  Task        string         `gorm:"size:255"`
  IsCompleted bool           `gorm:"default:false"`
  CreatedAt   time.Time      `gorm:"default:CURRENT_TIMESTAMP"`
  UpdatedAt   time.Time      `gorm:"default:CURRENT_TIMESTAMP"`
}
上記のコードの解説をしていきます。
IDフィールド:
uint 型(符号なし整数)で、タスクの一意な識別子として機能します。
gorm:"primary_key" タグは、このフィールドがテーブルの主キーであることを示します。
Taskフィールド:
string 型で、タスクの内容や説明を格納します。
gorm:"size:255" タグは、このフィールドが最大255文字の長さを持つことを示します。
IsCompletedフィールド:
bool 型(真偽値)で、タスクが完了したかどうかを示します。
gorm:"default:false" タグは、新しいレコードが作成されたときにこのフィールドのデフォルト値が false であることを示します。
CreatedAtフィールド:
time.Time 型で、タスクが作成された日時を格納します。
gorm:"default:CURRENT_TIMESTAMP" タグは、新しいレコードが作成されたときに現在のタイムスタンプを自動的にこのフィールドに設定することを示します。
UpdatedAtフィールド:
time.Time 型で、タスクが最後に更新された日時を格納します。
gorm:"default:CURRENT_TIMESTAMP" タグは、レコードが更新されるたびに現在のタイムスタンプを自動的にこのフィールドに設定することを示します。
続いて、先ほどのdb.AutoMigrate()の引数に&Task{}を書きます。
db.AutoMigrate(&Task{})
3.4.3 エンドポイントを作成しよう!
続いて、CRUD操作のAPIを作成していきます。
まずは、Taskを取得するエンドポイントです。
main.goのmain.goのr := gin.Default()の下に書いてください。
// タスクを取得するエンドポイント
r.GET("/tasks", func(c *gin.Context) {
  var tasks []Task
  db.Find(&tasks)
  c.JSON(http.StatusOK, tasks)
})
- 
r.GET("/tasks", ...): この行は、URLパスが /tasks であるGETリクエストをハンドルするためのルートを定義しています。クライアントがこのエンドポイントにリクエストを送ると、指定された無名関数が実行されます。 
- 
var tasks []Task: Task 型のスライスを宣言します。このスライスは、データベースから取得されるタスクを格納するために使用されます。 
- 
db.Find(&tasks): GORMの Find メソッドを使用して、データベース内の全ての Task レコードを取得し、それらを tasks スライスに格納します。 
- 
c.JSON(http.StatusOK, tasks): Ginの JSON メソッドを使用して、ステータスコード 200 OK と共に取得したタスクのリストをJSON形式でクライアントに返します。 
続いて、TaskをCreateするエンドポイントを作成します。
先ほどのGetの下に記述してください。
// 新しいタスクを作成するエンドポイント
r.POST("/tasks", func(c *gin.Context) {
  var task Task
  if err := c.ShouldBindJSON(&task); err != nil {
	c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	return
  }
  db.Create(&task)
  c.JSON(http.StatusOK, task)
})
ここでは、Gormの機能である、db.Create(&task)を用いて、taskをpostしています。
続いて、TaskをUpdateするエンドポイントを作成します。
先ほどのPostの下に記述してください。
// タスクを更新するエンドポイント
r.PUT("/tasks/:id", func(c *gin.Context) {
  var task Task
  id := c.Param("id")
  if err := db.First(&task, id).Error; err != nil {
	c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
	return
  }
  if err := c.ShouldBindJSON(&task); err != nil {
	c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	return
  }
  db.Save(&task)
  c.JSON(http.StatusOK, task)
})
- 
var task Task: Task 型の変数を宣言します。この変数は、データベースから取得される特定のタスクを格納するために使用されます。 
 id := c.Param("id"): リクエストURLからタスクのIDを取得します。
 db.First(&task, id): GORMの First メソッドを使用して、指定されたIDを持つ最初の Task レコードを取得し、それを task 変数に格納します。レコードが見つからない場合はエラーを返します。
- 
db.Save(&task): GORMの Save メソッドを使用して、更新された task をデータベースに保存します。 
 c.JSON(http.StatusOK, task): 更新されたタスクをJSON形式でクライアントに返します。
最後に、Deleteのエンドポイントを作成します。
先ほどのUpdateの下に記述してください。
// タスクを削除するエンドポイント
r.DELETE("/tasks/:id", func(c *gin.Context) {
  var task Task
  id := c.Param("id")
  if err := db.First(&task, id).Error; err != nil {
	c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
	return
  }
  db.Delete(&task)
  c.JSON(http.StatusOK, gin.H{"message": "Task deleted"})
})
updateと同じように、id := c.Param("id")でidを取得し、Gormの機能であるdb.Delete(&task)で削除しています。
以上でCRUD操作のエンドポイントの作成ができました!!
3.5 テスト
APIの各エンドポイントをテストし、期待通りに動作することを確認します。
今回は、VSCodeの拡張機能であるThunder Clientを使用してテストを行いました。
- 
POST 
  
- 
GET 
  
- 
PUT 
  
- 
DELETE 
  
4. まとめ
この記事では、Go言語、Gin、Gorm、およびPostgreSQLを使用して簡単なREST APIを構築する方法を学びました。
これらの技術を組み合わせることで、効率的でスケーラブルなWebアプリケーションを構築することができます。
今回のソースコードは以下になります!
5. 次回予告
次回のエピソードでは、今回構築したエンドポイントをさらに洗練させ、アプリケーションの構造を一段と強化します。具体的には、構造体を専用のmodelディレクトリに整理し、レイヤードアーキテクチャの原則に基づいた設計へと進化させていきます。これにより、よりモジュラーで、メンテナンスしやすく、拡張性の高いアプリケーションを目指します。
次回もお楽しみに!
6. 参考文献





Discussion