Docker環境にGo(Air/Echo/Gorm)でAPIを作る
はじめに
Go学習の一環として、Goのメジャーなパッケージ(Air, Echo,Gorm)を使ってDocker環境にAPIとDB(MySQL)を作ります。
作業環境
分類 | バージョンなど |
---|---|
PC | mac(m1) |
OS | Sonoma 14.2.1 |
Go | 1.21.4 |
Docker | 24.0.7 |
docker-compose | 2.23.3-desktop.2 |
最終的なディレクトリ構成
.
├── app
│ ├── db
│ │ └── db.go
│ ├── models
│ │ └── user.go
│ ├── routes
│ │ ├── routes.go
│ │ └── user.go
│ ├── .air.toml
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── db
│ ├── initdb.d
│ │ └── user.sql
│ └── conf.d
│ │ └── custom.cnf
├── env
│ ├── api.env
│ └── db.env
├── docker-compose.yml
└── Dockerfile
作成手順
手順1: Dockerの準備
- 以下のディレクトリを作成
.
├── app
├── db
└── env
2. API用にDockerfileを作成
# goのイメージをDockerHubから流用する(Alpine Linux)
FROM golang:1.21.5-alpine3.18
# Linuxパッケージ情報の最新化+gitがないのでgitを入れる
RUN apk update && apk add git
# ログのタイムゾーンを指定
ENV TZ /usr/share/zoneinfo/Asia/Tokyo
# コンテナ内の作業ディレクトリを指定
WORKDIR /app
# ソースコードをコンテナ内にコピー
COPY /app/* ./
# /app/go.modに記載された依存関係の解決+必要なパッケージのダウンロードを実行
RUN go mod download
# Airのバイナリをインストール
RUN go install github.com/cosmtrek/air@latest
# コンテナの公開するポートを指定
EXPOSE 5050
# 起動時のコマンド(airを使用するため)
CMD ["air", "-c", ".air.toml"]
3. APIとDB用にdocker-compose.ymlファイルを作成
version: '3.9' # docker-compose.ymlファイルの構文バージョン
services:
# DB用コンテナ作成
db:
container_name: sample-db
# イメージの指定(docker-hubから直接流用)
image: 'mysql:8.2.0'
# DBデータ保持用のボリュームをバインド
volumes:
- sample_db_data:/var/lib/mysql
env_file:
- ./env/db.env # 環境変数ファイルへのパス
# API用コンテナ作成
api:
container_name: sample-api
build: . # イメージのビルドに使用するDockerfileへの相対パス
volumes:
# バインドマウント
- type: bind
source: ./app
target: /app
ports:
- 5050:5050
env_file:
- ./env/api.env
# 依存するサービス名(先に起動させたいサービス)
depends_on:
- db
# DBデータ保持用のボリューム
volumes:
sample_db_data:
【現時点でのディレクトリ】
.
├── app 【New】
├── db 【New】
├── env 【New】
├── docker-compose.yml 【New】
└── Dockerfile 【New】
手順2: データベースの準備(MySQL)
1. envディレクトリにdb.env
ファイルを作成
# イメージ構築時に最初に作成されるDB名
MYSQL_DATABASE=sample
# rootユーザのパスワード
MYSQL_ROOT_PASSWORD=root
# タイムゾーンの設定
TZ=Asia/Tokyo
2. dbディレクトリにMySQLの設定ファイルcustom.cnf
を作成
データベースが日本語に対応するよう設定ファイルを追加する。
ファイルを追加するディレクトリは db/conf.d/
とする。
[mysqld]
character-set-server=utf8
collation-server=utf8_general_ci
[client]
default-character-set=utf8
-
character-set-server=utf8
MySQLサーバーのデフォルト文字セットをutf-8
に指定している。 -
collation-server=utf8_general_ci
MySQLサーバーのデフォルト照合順序をutf8_general_ci
に指定している。 -
default-character-set=utf8
MySQLクライアント接続のデフォルトの文字セットをutf-8
に指定している。
3. 初期データの作成
データベースに初期テーブルとデータを挿入するsqlを用意する。
ファイルを追加するディレクトリは db/initdb.d/
とする。
DROP TABLE IF EXISTS users;
CREATE TABLE IF NOT EXISTS users (
user_id VARCHAR(36) PRIMARY KEY,
user_name VARCHAR(256) NOT NULL,
age TINYINT UNSIGNED NOT NULL,
gender VARCHAR(256) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
INSERT INTO users (user_id, user_name, age, gender) VALUES (UUID(), '田中太郎', 40, 'male');
INSERT INTO users (user_id, user_name, age, gender) VALUES (UUID(),'小保方晴子', 30, 'female');
INSERT INTO users (user_id, user_name, age, gender) VALUES (UUID(),'佐村河内守', 60, 'male');
【現時点でのディレクトリ】
.
├── app
├── db
│ ├── initdb.d
│ │ └── users.sql 【New】
│ └── conf.d
│ │ └── custom.cnf 【New】
├── env
│ └── db.env 【New】
├── docker-compose.yml
└── Dockerfile
手順3: API作成の準備
1. dockerイメージ作成+コンテナ起動してAPI用コンテナのシェルにアクセス
- イメージのビルド、コンテナの起動
docker-compose up -d
- API用コンテナのシェルにアクセス
docker-compose exec -it api sh
シェル内ではデフォルトでDockerfileに指定したWORKDIRである/app
ディレクトリが表示される。
2. goプロジェクトの作成
- go.modファイルの作成
モジュールの定義と依存関係の管理を行うファイルを作成。
go mod init sample # sample はモジュール名
- main.goファイルの作成
アプリケーションのエントリーポイントとなるファイルを作成。
touch main.go
3. Goの各種パッケージをダウンロード
- Echo
- Gorm
- Gormの/MySQLドライバー
go get -u github.com/labstack/echo/v4 gorm.io/gorm gorm.io/driver/mysql
余談:go get/install/mod downloadの違い
※間違っていたらご指摘ください。
-
get
指定されたパッケージとその依存関係をgo.modファイルに記載する+ダウンロードする。 -
install
指定されたパッケージのバイナリをインストールする。airとか、開発環境に必要だけどアプリ内で使用するパッケージとしてはいらないものはこれを使う感じが良いかも。(go.modにも依存関係が記録されないため) -
mod download
go.modに記載されたパッケージをダウンロードする。実際go runで実行時にダウンロード、インストールは実行されるけど、事前にダウンロードしておきたい場合に使う。
4. Airの設定ファイルを配置
Airのgithubリポジトリからair_example.toml
をコピーしてappディレクトリに.air.toml
にrenameして配置し、内容を好みに応じて修正する。そのままでも良い。
修正内容
# アプリのルート
root = "."
# ビルド結果の保管場所
tmp_dir = "tmp"
[build]
cmd = "go build -o ./tmp/main ./main.go"
bin = "tmp/main"
# 検知の対象とする拡張子
include_ext = ["go", "tpl", "tmpl", "html"]
# 検知の対象外とするファイル
exclude_file = []
# 検知の対象外とするディレクトリ
exclude_dir = ["assets", "tmp"]
# 検知の対象外とする拡張子
exclude_regex = ["_test\\.go"]
# 変更されていないファイルに対するリロード有無
exclude_unchanged = true
# 参照先のファイルが変更された際のリロード有無
follow_symlink = true
# tmp_dirに配置されるビルド結果のログファイル
log = "air.log"
# fsnotifyを使わずにポーリングによる変更検知を行うか否か(Windows環境の場合はtrueにする必要あり。)
poll = false
# ポーリング時のインターバル(ms)
poll_interval = 0
# ファイル変更検知からビルドまでの遅延(ms)
delay = 0
# ファイル監視イベントの処理中にエラーが発生した場合、監視を止めるか否か
stop_on_error = true
# プロセスを修了する前にシグナルを送信するか否か
send_interrupt = false
# 実行中のアプリのダウンタイム
kill_delay = 500 # nanosecond
# 再実行の設定
rerun = false
# 再実行時の遅延(ms)
rerun_delay = 500
[log]
# ログへの時刻出力有無
time = false
# メインのみのログ設定
main_only = false
[color]
# ログのカラー設定
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"
[misc]
# 終了時にビルド先のディレクトリを削除するか否か
clean_on_exit = true
[screen]
# リビルド時のクリア設定
clear_on_rebuild = true
# 自動スクロール設定
keep_scroll = true
5. envディレクトリにapi.env
ファイルを用意
データベース接続に必要な環境変数を用意する。
DB_USER=root
DB_PASSWORD=root
DB_NAME=sample
【現時点でのディレクトリ】
.
├── app
│ ├── .air.toml 【New】
│ ├── go.mod 【New】
│ ├── go.sum 【New】
│ └── main.go 【New】
├── db
│ ├── initdb.d
│ │ └── user.sql
│ └── conf.d
│ │ └── custom.cnf
├── env
│ ├── api.env 【New】
│ └── db.env
├── docker-compose.yml
└── Dockerfile
手順4: API作成
1. db接続用のモジュール作成
package db
import (
"fmt"
"os"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
func Init() {
// api.envに定義したDB関係の環境変数を取得
dbUser := os.Getenv("DB_USER")
dbPassword := os.Getenv("DB_PASSWORD")
dbName := os.Getenv("DB_NAME")
// tcp()の中にdocker-composeで定義したDB用コンテナのサービス名を入れれば、
// 自動的にホストとポートを読み取ってくれる
dsn := fmt.Sprintf(
"%s:%s@tcp(db)/%s?charset=utf8mb4&parseTime=true&loc=Local",
dbUser,
dbPassword,
dbName,
)
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("Could not connect to database.")
}
}
2. modelsモジュールを作成
データモデルを定義するモジュールを作成する。
package models
import (
"sample/db"
"time"
"github.com/labstack/echo/v4"
)
type User struct {
// mysqlのカラム名をタグとして記載する
UserId string `json:"userId" mysql:"user_id"`
UserName string `json:"userName" mysql:"user_name"`
Age uint8 `json:"age" mysql:"age"`
Gender string `json:"gender" mysql:"gender"`
UpdatedAt time.Time `json:"updatedAt" mysql:"updated_at"`
CreatedAt time.Time `json:"createdAt" mysql:"created_at"`
}
// 全ユーザー取得処理
func GetAllUsers() ([]User, error) {
users := []User{}
if db.DB.Find(&users).Error != nil {
return nil, echo.ErrNotFound
}
return users, nil
}
// ユーザー取得処理(id)
func GetUserById(id string) (*User, error) {
user := User{}
if db.DB.Where("user_id = ?", id).First(&user).Error != nil {
return nil, echo.ErrNotFound
}
return &user, nil
}
3. routesモジュールを作成
ルーティングとハンドラ用のモジュールを作成する。
package routes
import (
"github.com/labstack/echo/v4"
)
func RegisterRoutes(server *echo.Echo) {
server.GET("/user", getAllUsers)
server.GET("/user/:userId", getUserById)
}
package routes
import (
"net/http"
"sample/models"
"github.com/labstack/echo/v4"
)
func getAllUsers(context echo.Context) error {
users, err := models.GetAllUsers()
// ※注意:エラーハンドリングはテキトーです
if err != nil {
return context.JSON(http.StatusInternalServerError, "ユーザーを取得できませんでした。")
}
return context.JSON(http.StatusOK, users)
}
func getUserById(context echo.Context) error {
userId := context.Param("userId")
user, err := models.GetUserById(userId)
// ※注意:エラーハンドリングはテキトーです
if err != nil {
return context.JSON(http.StatusInternalServerError, "ユーザーを取得できませんでした。")
}
return context.JSON(http.StatusOK, user)
}
4. main.goにてDB初期化処理とサーバー実行処理を記載
package main
import (
"sample/db"
"sample/routes"
"github.com/labstack/echo/v4"
)
func main() {
// DB接続
db.Init()
server := echo.New()
routes.RegisterRoutes(server)
server.Logger.Fatal(server.Start(":5050"))
}
以上で完成です。
実行してみる
コンテナ起動
docker-compose up -d
あとはブラウザにlocalhost:5050/user
と入力してデータが返ってきていればOK。
参考
Discussion