はじめに
今回は、Goの強力なWebフレームワーク「Echo」を使用して、PostgreSQLをバックエンドとするAPIサーバーを構築し、golang-migrate/migrate
を用いてデータベースのマイグレーションを管理する方法をご紹介します。
前提条件
以下のツールと環境がインストールされていることを確認してください。
ステップ1: プロジェクトのセットアップ
まず、プロジェクトのディレクトリを作成し、必要なパッケージをインストールします。
# プロジェクトディレクトリを作成
mkdir echo-migrate-tutorial
cd echo-migrate-tutorial
# Goモジュールの初期化
go mod init github.com/yourusername/echo-migrate-tutorial
# 必要なパッケージのインストール
go get github.com/labstack/echo/v4
go get github.com/golang-migrate/migrate/v4
go get github.com/golang-migrate/migrate/v4/database/postgres
go get github.com/golang-migrate/migrate/v4/source/file
go get github.com/lib/pq
go get github.com/spf13/viper
注:
github.com/yourusername/echo-migrate-tutorial
は適宜自身のGitHubユーザー名に置き換えてください。
ステップ2: 設定ファイルの作成
データベースの接続情報やその他の設定を管理するために、設定ファイルを作成します。ここでは、config.yaml
を使用します。
# 設定ファイル用のディレクトリを作成
mkdir config
config/config.yaml
を作成し、以下の内容を記述します。
# config/config.yaml
database:
user: your_db_user # PostgreSQLのユーザー名
password: your_db_password # PostgreSQLのパスワード
name: your_db_name # 使用するデータベース名
host: localhost # データベースホスト
port: 5432 # データベースポート
sslmode: disable # SSLモード設定
server:
port: 8080 # APIサーバーのポート番号
注意:
your_db_user
、your_db_password
、your_db_name
は自身の環境に合わせて設定してください。
ステップ3: データベース接続の設定
設定ファイルからデータベースの接続情報を読み取り、接続を確立するためのコードを作成します。config.go
というファイルを作成します。
// config/config.go
package config
import (
"database/sql"
"fmt"
"log"
"github.com/spf13/viper"
"github.com/lib/pq" // PostgreSQLドライバ
)
// Config構造体は設定ファイルの内容を保持します
type Config struct {
Database struct {
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Name string `mapstructure:"name"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
SSLMode string `mapstructure:"sslmode"`
} `mapstructure:"database"`
Server struct {
Port int `mapstructure:"port"`
} `mapstructure:"server"`
}
// LoadConfigは設定ファイルを読み込み、Config構造体を返します
func LoadConfig() *Config {
viper.SetConfigName("config") // 設定ファイル名(拡張子不要)
viper.SetConfigType("yaml") // 設定ファイルの形式
viper.AddConfigPath("./config") // 設定ファイルのパス
// 設定ファイルを読み込む
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("設定ファイルの読み込みエラー: %s", err)
}
var config Config
// 設定ファイルの内容をConfig構造体にマッピング
if err := viper.Unmarshal(&config); err != nil {
log.Fatalf("設定ファイルの解析エラー: %v", err)
}
return &config
}
// InitDBはデータベース接続を初期化し、*sql.DBを返します
func InitDB(cfg *Config) *sql.DB {
// PostgreSQL接続文字列をフォーマット
psqlInfo := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s",
cfg.Database.User,
cfg.Database.Password,
cfg.Database.Host,
cfg.Database.Port,
cfg.Database.Name,
cfg.Database.SSLMode,
)
// データベースに接続
db, err := sql.Open("postgres", psqlInfo)
if err != nil {
log.Fatal("データベース接続エラー:", err)
}
// データベース接続の確認
if err := db.Ping(); err != nil {
log.Fatal("データベースへのPingエラー:", err)
}
log.Println("データベースに接続しました。")
return db
}
解説
-
Config構造体:
config.yaml
の内容を保持するための構造体です。mapstructure
タグを使用して、Viperが設定ファイルのキーと構造体のフィールドをマッピングできるようにしています。 -
LoadConfig関数: Viperを使用して設定ファイルを読み込み、
Config
構造体にデータをマッピングします。設定ファイルの読み込みに失敗した場合は、アプリケーションを終了します。 -
InitDB関数:
Config
構造体からデータベース接続情報を取得し、PostgreSQLに接続します。接続に成功すると、*sql.DB
を返します。
ステップ4: マイグレーションファイルの作成
データベースのスキーマを管理するために、マイグレーションファイルを作成します。migrations
フォルダを作成し、初期マイグレーションを追加します。
mkdir migrations
例えば、users
テーブルを作成するマイグレーションを作成します。
migrate create -ext sql -dir migrations create_users_table
これにより、migrations
フォルダに以下のようなファイルが生成されます。
000001_create_users_table.up.sql
000001_create_users_table.down.sql
マイグレーションファイルの内容
migrations/000001_create_users_table.up.sql
-- migrations/000001_create_users_table.up.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY, -- ユーザーID
name VARCHAR(100) NOT NULL, -- ユーザー名
email VARCHAR(100) UNIQUE NOT NULL -- ユーザーのメールアドレス
);
migrations/000001_create_users_table.down.sql
-- migrations/000001_create_users_table.down.sql
DROP TABLE users; -- usersテーブルを削除
注:
migrate create
コマンドにより、自動的にタイムスタンプ付きのマイグレーションファイルが作成されます。
ステップ5: マイグレーションの適用
マイグレーションをデータベースに適用するためのコードをmigration/migration.go
に追加します。
// migration/migration.go
package migration
import (
"database/sql"
"fmt"
"log"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file" // ファイルソースのサポート
)
// RunMigrationsはマイグレーションを実行します
func RunMigrations(db *sql.DB) {
// PostgreSQL用のマイグレーションドライバーを作成
driver, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
log.Fatal("マイグレーションドライバーの初期化エラー:", err)
}
// マイグレーションインスタンスを作成
m, err := migrate.NewWithDatabaseInstance(
"file://migrations", // マイグレーションファイルのパス
"postgres", // データベース名
driver, // ドライバーインスタンス
)
if err != nil {
log.Fatal("マイグレーションインスタンスの作成エラー:", err)
}
// マイグレーションの実行
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
log.Fatal("マイグレーションの適用エラー:", err)
}
fmt.Println("マイグレーションが適用されました。")
}
解説
-
postgres.WithInstance: 既存の
*sql.DB
インスタンスからPostgreSQL用のマイグレーションドライバーを作成します。 - migrate.NewWithDatabaseInstance: マイグレーションファイルのソースとデータベースドライバーを指定して、マイグレーションインスタンスを作成します。
-
m.Up(): すべての未適用のマイグレーションを順番に適用します。既に最新のマイグレーションが適用されている場合は、
migrate.ErrNoChange
が返されます。
ステップ6: Echoサーバーの設定とAPIエンドポイントの作成
Echoフレームワークを使用して、APIサーバーとエンドポイントを設定します。main.go
に以下のコードを追加します。
// main.go
package main
import (
"fmt"
"log"
"net/http"
"github.com/labstack/echo/v4"
"github.com/yourusername/echo-migrate-tutorial/config"
"github.com/yourusername/echo-migrate-tutorial/migration"
)
// Userはユーザー情報を表す構造体です
type User struct {
ID int `json:"id"` // ユーザーID
Name string `json:"name"` // ユーザー名
Email string `json:"email"` // ユーザーのメールアドレス
}
func main() {
// 設定の読み込み
cfg := config.LoadConfig()
// データベース初期化
db := config.InitDB(cfg)
defer db.Close()
// マイグレーション実行
migration.RunMigrations(db)
// Echoインスタンス作成
e := echo.New()
// ルートエンドポイントの設定
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, Echo with Migrate!")
})
// ユーザー取得エンドポイントの設定
e.GET("/users", func(c echo.Context) error {
// usersテーブルから全ユーザーを取得
rows, err := db.Query("SELECT id, name, email FROM users")
if err != nil {
// クエリ実行時のエラーをJSON形式で返す
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
defer rows.Close()
var users []User
// 各行をUser構造体にマッピング
for rows.Next() {
var user User
if err := rows.Scan(&user.ID, &user.Name, &user.Email); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
users = append(users, user)
}
// ユーザーリストをJSON形式で返す
return c.JSON(http.StatusOK, users)
})
// ユーザー作成エンドポイントの設定
e.POST("/users", func(c echo.Context) error {
var user User
if err := c.Bind(&user); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}
// データベースにユーザーを挿入
err := db.QueryRow("INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id", user.Name, user.Email).Scan(&user.ID)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return c.JSON(http.StatusCreated, user)
})
// APIサーバーの起動
serverAddress := fmt.Sprintf(":%d", cfg.Server.Port)
if err := e.Start(serverAddress); err != nil {
log.Fatal("サーバー起動エラー:", err)
}
}
解説
-
User構造体:
users
テーブルの各レコードを表す構造体です。JSONタグを付与することで、APIレスポンス時に適切なキー名でデータが返されます。 -
main関数:
-
設定の読み込み:
config.LoadConfig()
を呼び出して、config.yaml
から設定を読み込みます。 -
データベース初期化:
config.InitDB(cfg)
を呼び出して、データベースに接続します。接続は関数の終了時に閉じられます。 -
マイグレーションの実行:
migration.RunMigrations(db)
を呼び出して、マイグレーションを適用します。 -
Echoインスタンスの作成:
echo.New()
でEchoのインスタンスを作成します。 -
ルートエンドポイント:
/
にアクセスすると「Hello, Echo with Migrate!」と表示されます。 -
ユーザー取得エンドポイント:
/users
にGETリクエストを送ると、users
テーブルの全ユーザー情報がJSON形式で返されます。 - サーバーの起動: 設定ファイルで指定したポート番号でAPIサーバーを起動します。
-
設定の読み込み:
ステップ7: サーバーの実行
go run main.go
※1 PostgreSQLのサービスが起動していること
※2 DB内のユーザー・テーブルが存在すること(存在しない場合、以下を参考にしてユーザー・テーブルを作成してください。)psql postgres CREATE ROLE your_db_user WITH LOGIN PASSWORD 'your_password'; ALTER ROLE your_db_user CREATEDB; ALTER ROLE your_db_user WITH SUPERUSER; CREATE DATABASE your_db_name OWNER your_db_user;
動作確認
curl
を使用してAPIエンドポイントの動作を確認します。
-
ルートエンドポイントの確認:
以下のコマンドをターミナルで実行します。
curl -i http://localhost:8080/
期待される出力:
HTTP/1.1 200 OK Content-Type: text/plain; charset=UTF-8 Date: Thu, 09 Nov 2024 12:00:00 GMT Content-Length: 23 Hello, Echo with Migrate!
この出力により、ルートエンドポイントが正しく動作していることが確認できます。
-
ユーザー取得エンドポイントの確認:
以下のコマンドを実行します。
curl -i http://localhost:8080/users
期待される出力:
HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Date: Thu, 09 Nov 2024 12:00:00 GMT Content-Length: 2 []
現時点では
users
テーブルにデータがないため、空の配列[]
が返されます。 -
ユーザーの追加:
オプションとして、ユーザーを追加するエンドポイントを作成し、
curl
を使用してユーザーを追加および取得する方法を紹介します。ユーザーの追加
以下の
curl
コマンドを実行して、新しいユーザーを追加します。curl -i -X POST http://localhost:8080/users \ -H "Content-Type: application/json" \ -d '{"name": "山田太郎", "email": "yamada@example.com"}'
期待される出力:
HTTP/1.1 201 Created Content-Type: application/json; charset=UTF-8 Date: Thu, 09 Nov 2024 12:05:00 GMT Content-Length: 49 { "id": 1, "name": "山田太郎", "email": "yamada@example.com" }
ユーザー取得の確認
再度、ユーザー取得エンドポイントを実行します。
curl -i http://localhost:8080/users
期待される出力:
HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Date: Thu, 09 Nov 2024 12:06:00 GMT Content-Length: 51 [ { "id": 1, "name": "山田太郎", "email": "yamada@example.com" } ]
この出力により、ユーザーが正しく追加され、取得できることが確認できます。
DB内のテーブル確認
PostgreSQLのテーブル内にデータが格納されていることをSQL実行して確かめます。
psql -h localhost -U sakae -d test
SELECT * FROM users;
id | name | email
----+------------+--------------------
1 | 山田太郎 | yamada@example.com
(1 rows)
最終的なディレクトリ構成
プロジェクトが完成した後のディレクトリ構成は以下のようになります。
echo-migrate-tutorial/
├── config
│ ├── config.yaml # 設定ファイル
│ └── config.go # 設定読み込みとDB初期化コード
├── go.mod # Goモジュールの依存関係
├── go.sum # Goモジュールのチェックサム
├── main.go # サーバーのエントリーポイントとAPIエンドポイント
├── migration
│ └── migration.go # マイグレーション実行コード
└── migrations
├── 000001_create_users_table.down.sql # マイグレーションのダウンスクリプト
└── 000001_create_users_table.up.sql # マイグレーションのアップスクリプト
-
config/: 設定ファイルと設定読み込み用のコードを格納します。
-
config.yaml
: データベースやサーバーの設定を記述。 -
config.go
: 設定ファイルを読み込み、データベース接続を初期化するコード。
-
-
migration/: マイグレーション関連のコードを格納します。
-
migration.go
: マイグレーションを実行する関数。
-
-
migrations/: マイグレーションファイルを格納します。
-
000001_create_users_table.up.sql
: マイグレーションのアップ(適用)スクリプト。 -
000001_create_users_table.down.sql
: マイグレーションのダウン(ロールバック)スクリプト。
-
- main.go: サーバーのエントリーポイントとAPIエンドポイントの定義。
- go.mod / go.sum: Goモジュールの依存関係を管理。
追加: マイグレーションのロールバック
必要に応じて、マイグレーションをロールバックすることもできます。以下のコマンドを使用します。
migrate -path migrations -database "postgres://your_db_user:your_db_password@localhost/your_db_name?sslmode=disable" down
注意:
your_db_user
、your_db_password
、your_db_name
は自身の環境に合わせて設定してください。
このコマンドを実行すると、最新のマイグレーションが1つロールバックされます。複数回ロールバックしたい場合は、-steps
フラグを使用してステップ数を指定できます。
# 2ステップ分ロールバック
migrate -path migrations -database "postgres://your_db_user:your_db_password@localhost/your_db_name?sslmode=disable" down -steps=2
まとめ
今回は、GoのEchoフレームワークを使用してPostgreSQL向けのAPIサーバーを構築し、golang-migrate/migrate
を用いてデータベースのマイグレーションを管理する方法を解説しました。
さらに、データベースの接続情報を設定ファイルに分離することで、設定管理をより効率的に行う方法もご紹介しました。以下のポイントを押さえました。
- プロジェクトのセットアップ: 必要なパッケージのインストールとGoモジュールの初期化。
-
設定ファイルの作成:
config.yaml
にデータベースやサーバーの設定を記述。 -
データベース接続の設定:
config.go
で設定ファイルを読み込み、PostgreSQLへの接続を初期化。 -
マイグレーションの作成と適用:
migrate
CLIを使用してマイグレーションファイルを作成し、コードで適用。 - Echoサーバーの設定: シンプルなAPIエンドポイントを作成。
- ディレクトリ構成: プロジェクトの最終的なフォルダ構成を確認。
- マイグレーションのロールバック: 必要に応じてマイグレーションをロールバックする方法。
この基本的なセットアップを基に、さらなる機能追加やエンドポイントの拡充、複雑なマイグレーションの管理など、プロジェクトを拡張していくことができます。