Contents
1. 開発環境のセットアップ
1‑1. Go のインストールとモジュール初期化
| OS | 推奨インストール方法 | コマンド例 |
|---|---|---|
| macOS | Homebrew | brew install go |
| Ubuntu / Debian 系 | APT(公式リポジトリ) | sudo apt update && sudo apt install golang-go |
| Windows | winget または MSI インストーラ | winget install GoLang.Go |
インストールが完了したら、バージョンの確認と モジュール初期化 を行います。
(バージョンは「最新安定版」を想定してください。)
|
1 2 3 4 5 6 7 8 9 |
# バージョン確認 go version # 作業ディレクトリ作成 & 移動 mkdir -p ~/work/hello-go-web && cd $_ # go.mod の生成 go mod init hello-go-web |
環境変数のポイント
GOROOTは Go 本体が自動で設定します。手動で書く必要はありません。GOPATHを明示したい場合はデフォルトの~/go(Linux/macOS)/%USERPROFILE%\go(Windows)が推奨です。
|
1 2 3 4 |
# 例: ~/.zshrc や ~/.bash_profile に追記 export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin |
※注意
Go のバージョンや依存ライブラリはgo.mod/go.sumが真の情報源です。記事中に特定バージョンを記載しないことで、将来的な更新にも耐える構成になります。
2. プロジェクト構成と最小サーバ実装
2‑1. 推奨ディレクトリレイアウト
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
hello-go-web/ ├─ cmd/ # アプリケーションのエントリポイント │ └─ main.go ├─ internal/ # プロジェクト内部でのみ利用するコード │ └─ handler/ │ ├─ health.go │ └─ item.go # 後述の CRUD ハンドラ例 ├─ pkg/ # 再利用可能なユーティリティ(任意) ├─ web/ │ ├─ static/ │ └─ templates/ ├─ go.mod └─ go.sum |
cmd/:実行ファイルを生成するパッケージ。main.goが唯一のエントリです。internal/:外部からはインポートできないことがコンパイラで保証され、境界が明確になります。pkg/:社内・社外問わず再利用したいユーティリティを置く場所です(必須ではありません)。
2‑2. 標準ライブラリだけで動かす最小サーバ
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// cmd/main.go package main import ( "log" "net/http" "hello-go-web/internal/handler" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/health", handler.Health) // ヘルスチェックだけ用意 server := &http.Server{ Addr: ":8080", Handler: mux, } log.Println("🚀 server listening on http://localhost:8080") if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("server error: %v", err) } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// internal/handler/health.go package handler import ( "encoding/json" "net/http" ) func Health(w http.ResponseWriter, _ *http.Request) { resp := map[string]string{"status": "ok"} w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(resp) } |
ポイント
-net/httpとServeMuxだけで動作するため、依存関係が最小。
- 後から Gorilla Mux や chi に置き換えても、インターフェースは同じなので移行が容易です。
3. ルーティング・テンプレート・データ永続化
3‑1. ルーティングライブラリの選択肢
| ライブラリ | 特徴 |
|---|---|
| Gorilla Mux | 正規表現や変数抽出が得意。既存プロジェクトとの互換性が高い。 |
| chi | ミドルウェアチェーンが軽量で高速。マイクロサービスに向く。 |
どちらも go get だけで最新バージョンを取得できます(バージョンは go.mod が管理)。
|
1 2 3 4 5 6 |
# Gorilla Mux のインストール例 go get github.com/gorilla/mux # chi のインストール例 go get github.com/go-chi/chi/v5 |
実装サンプル(Gorilla Mux)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// cmd/main.go(Mux 版) package main import ( "net/http" "github.com/gorilla/mux" "hello-go-web/internal/handler" ) func main() { r := mux.NewRouter() r.HandleFunc("/health", handler.Health).Methods(http.MethodGet) // CRUD 用エンドポイント例(後述のハンドラ実装を参照) r.HandleFunc("/items", handler.GetItems).Methods(http.MethodGet) r.HandleFunc("/items/{id:[0-9]+}", handler.GetItem).Methods(http.MethodGet) http.ListenAndServe(":8080", r) } |
実装サンプル(chi)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// cmd/main.go(chi 版) package main import ( "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "hello-go-web/internal/handler" ) func main() { r := chi.NewRouter() r.Use(middleware.Logger) // リクエストログ出力 r.Get("/health", handler.Health) // 省略: CRUD エンドポイント http.ListenAndServe(":8080", r) } |
3‑2. HTML テンプレートと埋め込み静的ファイル
Go 1.16 以降は embed.FS により、ビルド時にテンプレートや CSS/JS をバイナリへ組み込めます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// cmd/main.go(embed の例) package main import ( "embed" "html/template" "net/http" "github.com/go-chi/chi/v5" "hello-go-web/internal/handler" ) //go:embed web/templates/*.html web/static/* var assets embed.FS func main() { r := chi.NewRouter() // テンプレート読み込み(キャッシュは任意で実装可) tmpl := template.Must(template.ParseFS(assets, "web/templates/*.html")) r.Get("/items", func(w http.ResponseWriter, r *http.Request) { items, _ := handler.FetchAll() _ = tmpl.ExecuteTemplate(w, "list.html", items) }) // 静的ファイル配信 fileServer := http.FileServer(http.FS(assets)) r.Handle("/static/*", http.StripPrefix("/static/", fileServer)) r.Get("/health", handler.Health) http.ListenAndServe(":8080", r) } |
web/templates/list.html
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Item List</title> <link rel="stylesheet" href="/static/style.css"> </head> <body> <h1>Items</h1> <ul> {{range .}} <li>{{.ID}}: {{.Name}}</li> {{else}} <li>データがありません。</li> {{end}} </ul> </body> </html> |
3‑3. データ永続化(SQLite と PostgreSQL)
ライブラリのインストール(バージョン指定なし)
|
1 2 3 |
go get github.com/mattn/go-sqlite3 # SQLite 用ドライバ (cgo 必要) go get github.com/jackc/pgx/v5 # PostgreSQL 用ドライバ |
コード例
|
1 2 3 4 5 6 7 8 9 10 11 12 |
// internal/db/sqlite.go package db import ( "database/sql" _ "github.com/mattn/go-sqlite3" ) func OpenSQLite(path string) (*sql.DB, error) { return sql.Open("sqlite3", path) } |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
// internal/db/postgres.go package db import ( "context" "github.com/jackc/pgx/v5/pgxpool" ) func OpenPostgres(dsn string) (*pgxpool.Pool, error) { return pgxpool.New(context.Background(), dsn) } |
ポイント
-database/sqlとpgxはインターフェースが似通っているので、ハンドラ側は「DB インタフェース」だけを依存させれば、SQLite ↔ PostgreSQL の切り替えが容易です。
4. テスト・並行処理・ビルド
4‑1. ハンドラのテーブル駆動テスト(httptest)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// internal/handler/health_test.go package handler import ( "net/http" "net/http/httptest" "testing" ) func TestHealth(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/health", nil) rec := httptest.NewRecorder() Health(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200 OK, got %d", rec.Code) } const want = `{"status":"ok"}` if body := rec.Body.String(); body != want+"\n" { // json.Encoder は改行を付加 t.Errorf("unexpected body: %s", body) } } |
CI では go test ./... がすべてのテストを走らせます。
4‑2. 背景ジョブ(goroutine + channel)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// internal/worker/worker.go package worker import ( "context" "log" "time" ) type Job struct { ID int Name string } // StartWorker は受信したジョブを順次処理します。 func StartWorker(ctx context.Context, jobs <-chan Job) { go func() { for { select { case job := <-jobs: log.Printf("process job %d: %s", job.ID, job.Name) time.Sleep(500 * time.Millisecond) // 疑似的な負荷 case <-ctx.Done(): log.Println("worker stopped") return } } }() } |
cmd/main.go で起動例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() jobCh := make(chan worker.Job, 10) worker.StartWorker(ctx, jobCh) for i := 1; i <= 5; i++ { jobCh <- worker.Job{ID: i, Name: fmt.Sprintf("Task-%d", i)} } // 以降はサーバ起動や他ロジックへ移行 } |
参考:本実装は Sophiate の「非エンジニアでも始められる Go Web 開発」記事([Sophiate 記事参照])でも同様のパターンが紹介されています。
4‑3. クロスコンパイル(cgo 非使用前提)
|
1 2 3 4 5 6 |
# Linux → Windows (amd64) GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o hello-go-web.exe ./cmd # macOS → Linux (arm64) (cgo が無い環境でのみ可能) GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o hello-go-web ./cmd |
4‑4. マルチステージ Dockerfile(軽量ランタイム)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# ---------- ビルドステージ ---------- FROM golang:1-alpine AS builder WORKDIR /src # モジュールキャッシュを有効にするため、go.mod と go.sum を先にコピー COPY go.mod go.sum ./ RUN go mod download COPY . . ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64 RUN go build -o /app/hello-go-web ./cmd # ---------- ランタイムステージ ---------- FROM alpine:3.19 WORKDIR /app COPY --from=builder /app/hello-go-web . EXPOSE 8080 ENTRYPOINT ["./hello-go-web"] |
ビルド手順:
|
1 2 3 |
docker build -t yourname/hello-go-web:latest . docker run -p 8080:8080 yourname/hello-go-web:latest |
5. CI/CD と本番デプロイ
5‑1. GitHub リポジトリの作成とブランチ戦略
|
1 2 3 4 5 6 |
git init git remote add origin https://github.com/<yourid>/hello-go-web.git git add . git commit -m "Initial commit" git push -u origin main |
main:本番デプロイ対象の安定ブランチfeature/*:新機能や改修はプルリクエスト経由でmainにマージ
5‑2. GitHub Actions ワークフロー(.github/workflows/ci.yml)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
name: CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: '^1.22' # 「最新安定版」範囲を指定 - name: Cache modules uses: actions/cache@v3 with: path: | ~/.cache/go-build ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - name: Run tests run: go test ./... - name: Build binary (Linux) env: CGO_ENABLED: 0 GOOS: linux GOARCH: amd64 run: go build -o hello-go-web ./cmd - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build & push Docker image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile push: true tags: yourdockerhub/hello-go-web:${{ github.sha }} |
5‑3. 本番デプロイ例(Docker Compose)
|
1 2 3 4 5 6 7 8 |
version: "3.9" services: web: image: yourdockerhub/hello-go-web:latest ports: - "80:8080" restart: unless-stopped |
docker compose up -d とすれば、数秒で本番環境が立ち上がります。
Kubernetes 環境へ展開する場合は Deployment と Service のマニフェストを追加すれば同様に動作します。
まとめ
| フェーズ | 主なポイント |
|---|---|
| 環境構築 | バイナリ/パッケージマネージャで Go をインストール → go.mod 初期化 |
| プロジェクトレイアウト | cmd / internal / pkg の三層構成が保守性とテスト容易性を向上 |
| サーバ実装 | 標準 net/http + ServeMux(必要に応じて Gorilla Mux / chi) |
| テンプレート & 静的資産 | html/template と embed.FS により単一バイナリで配布可能 |
| 永続化 | SQLite(開発向け)と PostgreSQL(本番向け)を共通インタフェースで扱う |
| テスト | httptest でハンドラ単体テスト、テーブル駆動で網羅的に検証 |
| 並行処理 | goroutine + channel によるバックグラウンドジョブ実装例 |
| ビルド & デプロイ | クロスコンパイルとマルチステージ Dockerfile で軽量イメージ化 |
| CI/CD | GitHub Actions がテスト・ビルド・Docker イメージ公開を自動化 |
この一連の手順を実践すれば、Go 言語だけでフロントエンドからデータ永続化、CI/CD まで完結する Web アプリケーション を構築できます。ぜひローカル環境で試しながら、自分のプロジェクトに合わせてカスタマイズしてみてください。
本稿は執筆時点の最新情報をもとに作成していますが、依存ライブラリやツールのバージョンは go.mod / go.sum が唯一の真実です。定期的に go get -u ./... で更新し、CI のテストが通ることを確認してください。