【Gin+Docker】Railsライクなディレクトリ構成で簡単なアプリを作ってみる (1)環境構築/Hello,world編
最近Go+Ginについて勉強していて、自分自身の備忘録的なノリで書いています。
結構我流なところあるので、間違っている部分等あれば指摘していただければ幸いです。
想定読者
- とりあえずGinを使ってみたいと思っている人
今回の最終目標
以下のような簡単なアプリを作ります。
- ユーザ名&パスワードでユーザ登録ができる
- ログインしたユーザがタイトル&本文で記事投稿ができる
- 以上の2つをGUIで行える。
記事構成
各トピックごとに分けて投稿していこうと思います。
- 環境構築/Hello,world編(本記事)
- ルーティング/controller編
- database設定/model編
- view編
- middleware編
最終的なディレクトリ構成
全記事が終わった時点で、このようなディレクトリ構成になる予定です。なお、ファイルについては省略しています。
root/
├ docker/
│ └ go/ # docker関係
└ src/
├ app/
│ ├ assets/
│ │ ├ images/ # ロゴなどの画像置き場
│ │ └ stylesheets/ # CSS置き場
│ ├ controllers/ # コントローラ
│ ├ middlewares/ # ミドルウェア
│ ├ models/ # モデル
│ └ templates/ # views用テンプレートファイル
├ config/ # サービス起動時に読み込みたい設定ファイル置き場
└ database/ # データベース接続処理、SQL系の記述など
前提条件
dockerおよびdocker-composeコマンドが動く環境である
環境構築
条件
今回、以下の項目を満たすように環境を構築していきたいと思います。
- dockerを使用し、ローカルのGoの環境を汚さない
- go.sumおよびgo.modは初回のみ作成し、以後は作成しない
- ホットリロードできるようにする
Dockerfile
まず今回使うDockerfileを作っていきます。docker/go/
ディレクトリに配置します。
FROM golang:1.20-alpine
ENV WORKDIR /go/src
WORKDIR $WORKDIR
COPY src/go.mod go.mod
COPY src/go.sum go.sum
ENTRYPOINT ["sh", "start.sh"]
dockerイメージは記事執筆時点で最新の1.20系かつ軽量のalpineを使います。
WORKDIRを/go/src
に設定しておきます。これで今後のコマンドは/go/src
で実行されるようになります。
初回のみ実行したいgo mod init
だけ実行しておき、ENTRYPOINTには、後ほど作るstart.sh
を実行するように設定しておきます。
docker-compose.yml
次に、docker-compose.ymlを作ります。このファイルはプロジェクトディレクトリ直下に配置します。
version: '3'
services:
go:
build:
context: .
dockerfile: docker/go/Dockerfile
volumes:
- "./src:/go/src"
ports:
- 8080:8080
ここでのポイントは以下の点です。
- 先ほど作成したDockerfileを使ってビルドする
- ポートフォワードでホストのポート8080とコンテナの8080を繋げる
- プロジェクト以下の
/src
ディレクトリをコンテナの/go/src
にマウントする
start.sh
dockerコンテナ起動時に実行するコマンドを記述します。このファイルはsrc/
ディレクトリに配置します。
#! /bin/sh
go mod tidy
go run main.go
go mod tidy
およびgo run main.go
は起動時は毎回実行したいので、Dockerfileではなくこちらに記述します。
なお、start.shのパーミッションが644などで実行ができない場合は、$ sudo chmod 777 src/start.sh
を実行するなどしてパーミッションを書き換えておきます。
Hello, world!を表示する
main.go
とりあえずhttp://localhost:8080
にアクセスするとHello, world!
を返すだけのmain.goを作ります。このファイルはsrc/
ディレクトリに配置します。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
engine := gin.Default()
engine.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello, world!")
})
engine.Run(":8080")
}
今回使うフレームワークのGinおよびhttpクライアントなどの処理が使えるnet/http
パッケージをインポートしています。
gin.Default()
で*gin.Engine
型のインスタンスengineを作ります。これはルーティングや、テンプレートHTML、静的な画像やcssなどのアセットの登録などが行える重要なインスタンスです。
次にengine.GET("/", ...)
で/
にGETメソッドでアクセスした際のルーティングを登録しています。
*gin.Context
型のインスタンスcは、リクエストパラメータなどの情報を取得する際に使用します。
今回はHello, world!
の文字列を返すだけにしています。
最後にengine.Run(":8080")
で、8080番ポートを使用してサーバにアクセスできるようにします。
実行
ここまで進めたところで、ファイルはそれぞれ以下のように配置されていると思います。
root/
├ docker/
│ └ go/
│ └ Dockerfile
├ src/
│ ├ main.go
│ └ start.sh
└ docker-compose.yml
$ cd src && go mod init gin_sample && go mod tidy && cd ..
を実行し、src/ディレクトリにgo.sumおよびgo.modを作成します。
$ docker-compose up
コマンドで実行し、うまくいけばブラウザでlocalhost:8080
にアクセスするとHello, world!
の文字列が表示されます。
ホットリロードを有効にする
このままでは、ホットリロードが有効になっていません。そのため、dockerコンテナ起動中にコードを書き換えても変更は反映されません(main.goのHello, world!
の文言を変えると確認できると思います)。
開発時、変更がある度にいちいちdockerコンテナを落として立ち上げ直して、などとやるのは手間なので、airというパッケージを使ってホットリロードするようにします。
air.toml
まず、airのサンプル設定ファイルがあるので、それをまるっとコピーしてsrc/air.toml
として保存しておきます。
Dockerfile
docker/go/Dockerfile
を以下のように変更して、airのinstallを追加します。
FROM golang:1.22-alpine # 1.20から1.22に変更
ENV WORKDIR /go/src
WORKDIR $WORKDIR
COPY src/go.mod go.mod
COPY src/go.sum go.sum
RUN go install github.com/cosmtrek/air@latest # 追加
ENTRYPOINT ["sh", "start.sh"]
start.sh
起動コマンドをairに変更します。
#! /bin/sh
air air.toml
実行
$ docker-compose up
を実行し、サーバを起動します。
起動中にmain.goのHello, world!
をHello, hoge!
等に変更したのち、ブラウザのページを更新して表示される文字列が変わっていれば成功です!
コンテナ外からの変更だとホットリロードがうまくいかない
筆者の環境では、普通にVSCode上でコードを変更しても、airが変更を認識せずホットリロードがうまく動いてくれませんでした。
いろいろ調べたら、コンテナ内での変更であればうまくいくようでした。
なので、VSCodeの拡張Remote Explorerを使って、コンテナにアタッチすることでVSCodeを使いつつホットリロードできるようになりました。
(2024/4/10追記)
筆者はM1 Macbook上でcolimaを使ってdockerを動かしており、その環境だとairのホットリロードがうまく効かなかったようです。
2023年12月にこちらの問題は解消し、air.tomlでpoll=true
にすることによりホットリロードが効くようになりました!
終わりに
ここまでのコードは以下に置いています。
リポジトリ
今回のコミット
次の記事は、ルーティング/controller編を予定しています。
ここまで読んでくださりありがとうございました。
Discussion