取り組みの背景:なぜ今、AntigravityでGoバックエンドなのか
AIネイティブなIDE「Antigravity」は、TypeScript/JavaScript の開発体験を大きく変えましたが、その恩恵はGo言語でも同様に享受できます。静的型付け・高いコンパイル速度・優れた並行処理モデルを持つGoは、マイクロサービスやサーバーレス環境での採用が急増しており、2026年時点でバックエンド開発者の必須スキルの一つに数えられます。
ここではAntigravityのAgent機能・コンテキスト理解・AIコード補完をフル活用して、本番品質のGo RESTful APIを0から構築するための完全ガイドを提供します。単なるチュートリアルではなく、実際の本番環境で通用する設計思想と実装パターンを体系的に学べます。
対象読者
- GoとAntigravityの基礎を習得済みで、本番レベルの実装に進みたい方
- TypeScript/Node.jsバックエンドからGoへの移行を検討している方
- マイクロサービス設計・API設計のベストプラクティスを深掘りしたい方
- AI IDEを使ってGoの生産性を10倍に引き上げたい方
この記事で構築するもの
ユーザー管理・認証・投稿管理機能を持つRESTful APIサーバーを構築します。技術スタックは以下の通りです。
- フレームワーク: Echo v4(高性能・シンプルなルーティング)
- DB: PostgreSQL + sqlc(型安全なSQLクエリ生成)
- 認証: JWT(JSON Web Token)
- テスト: testify + testcontainers-go
- デプロイ: Cloud Run(Google Cloud)または Fly.io
- AI支援: Antigravity Agent・コード補完・エラー診断
環境準備と Antigravity プロジェクト設定
必要なツールのインストール
# Go 1.22以降をインストール(公式サイトより)
go version # go version go1.22.x
# sqlc インストール(型安全なSQL→Goコード生成)
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
# migrate インストール(DBマイグレーション管理)
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# air インストール(ホットリロード開発)
go install github.com/air-verse/air@latestAntigravity での AGENTS.md 設定
プロジェクトルートに AGENTS.md を作成し、Antigravity Agentにプロジェクトコンテキストを与えます。これが生産性を劇的に上げる鍵です。
# Project Context
## Overview
Go RESTful API server using Echo v4, sqlc, PostgreSQL.
This is a production-grade backend with JWT authentication.
## Architecture
- cmd/server/main.go — エントリーポイント
- internal/api/ — HTTPハンドラー・ルーティング
- internal/db/ — sqlc生成コード(編集禁止)
- internal/service/ — ビジネスロジック
- internal/middleware/ — カスタムミドルウェア
- db/migrations/ — SQLマイグレーションファイル
- db/query/ — sqlcクエリ定義
## Conventions
- エラーはerrors.Wrapでスタックトレース付与
- ロガーはslog(標準ライブラリ)を使用
- テストはtestifyアサーションを使用
- DBテストはtestcontainersで実際のPostgreSQLを起動
## Database
- PostgreSQL 16(sqlcで型安全クエリ生成)
- マイグレーションはgolang-migrateで管理
## Do not
- internal/db/ ディレクトリを直接編集しない(sqlcが生成)
- グローバル変数を使用しないこの AGENTS.md があると、Antigravity AgentがGoの慣習・プロジェクト構造・命名規則を理解した上でコード生成してくれます。
プロジェクト構造の設計
ディレクトリ構成
Antigravity の Agent に「このプロジェクトのディレクトリ構造を作成して」と依頼すると、Go標準の慣習に沿った構造を提案してくれます。以下が推奨構成です。
myapi/
├── AGENTS.md
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── api/
│ │ ├── handler/
│ │ │ ├── user_handler.go
│ │ │ └── post_handler.go
│ │ ├── middleware/
│ │ │ ├── auth.go
│ │ │ └── logger.go
│ │ └── router.go
│ ├── db/
│ │ └── (sqlc生成ファイル — 編集禁止)
│ └── service/
│ ├── user_service.go
│ └── post_service.go
├── db/
│ ├── migrations/
│ │ ├── 000001_create_users.up.sql
│ │ └── 000001_create_users.down.sql
│ └── query/
│ ├── users.sql
│ └── posts.sql
├── sqlc.yaml
├── go.mod
└── .air.toml
go.mod の初期化
mkdir myapi && cd myapi
go mod init github.com/yourname/myapi
# 依存関係の追加
go get github.com/labstack/echo/v4
go get github.com/labstack/echo-jwt/v4
go get github.com/golang-jwt/jwt/v5
go get github.com/jackc/pgx/v5
go get github.com/golang-migrate/migrate/v4
go get github.com/joho/godotenv
go get go.uber.org/zapDBスキーマ設計と sqlc による型安全クエリ
マイグレーションファイルの作成
Antigravity に「ユーザーと投稿のERD設計と、golang-migrate形式のSQLを生成して」と指示します。生成されたファイルを確認・修正します。
-- db/migrations/000001_create_users.up.sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_users_email ON users (email);
CREATE INDEX idx_users_username ON users (username);
-- db/migrations/000002_create_posts.up.sql
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
published BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_posts_user_id ON posts (user_id);sqlc.yaml の設定
# sqlc.yaml
version: "2"
sql:
- engine: "postgresql"
queries: "db/query/"
schema: "db/migrations/"
gen:
go:
package: "db"
out: "internal/db"
emit_json_tags: true
emit_prepared_queries: true
emit_interface: true
emit_exact_table_names: falseSQLクエリ定義
-- db/query/users.sql
-- name: CreateUser :one
INSERT INTO users (email, username, password)
VALUES ($1, $2, $3)
RETURNING *;
-- name: GetUserByEmail :one
SELECT * FROM users
WHERE email = $1 LIMIT 1;
-- name: GetUserByID :one
SELECT * FROM users
WHERE id = $1 LIMIT 1;
-- name: ListUsers :many
SELECT id, email, username, created_at FROM users
ORDER BY created_at DESC
LIMIT $1 OFFSET $2;# sqlcによるGoコード生成
sqlc generate
# → internal/db/ に型安全なGoコードが生成されるAntigravity はこの生成ファイルを読み込んで「db.CreateUserParams 型を使ってユーザー作成処理を書いて」と指示するだけで、正確なGoコードを生成してくれます。これがsqlcとAntigravityの組み合わせの真価です。
Echo を使った API 実装
エントリーポイント(cmd/server/main.go)
package main
import (
"context"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/joho/godotenv"
"github.com/yourname/myapi/internal/api"
)
func main() {
_ = godotenv.Load()
// ロガー設定(構造化ログ)
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
// DBコネクションプール
pool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL"))
if err != nil {
slog.Error("DB接続失敗", "error", err)
os.Exit(1)
}
defer pool.Close()
// Echoサーバー起動
e := api.NewRouter(pool)
srv := &http.Server{
Addr: ":" + os.Getenv("PORT"),
Handler: e,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// グレースフルシャットダウン
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
slog.Error("サーバーエラー", "error", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
slog.Info("サーバーを正常終了しました")
}ルーター設定(internal/api/router.go)
package api
import (
"github.com/jackc/pgx/v5/pgxpool"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
echojwt "github.com/labstack/echo-jwt/v4"
"github.com/yourname/myapi/internal/api/handler"
mw "github.com/yourname/myapi/internal/middleware"
)
func NewRouter(pool *pgxpool.Pool) *echo.Echo {
e := echo.New()
// グローバルミドルウェア
e.Use(middleware.RequestID())
e.Use(mw.StructuredLogger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
// ハンドラー初期化
userH := handler.NewUserHandler(pool)
postH := handler.NewPostHandler(pool)
// 公開エンドポイント
v1 := e.Group("/api/v1")
v1.POST("/auth/register", userH.Register)
v1.POST("/auth/login", userH.Login)
// 認証必須エンドポイント
auth := v1.Group("")
auth.Use(echojwt.WithConfig(echojwt.Config{
SigningKey: []byte(os.Getenv("JWT_SECRET")),
}))
auth.GET("/users/me", userH.GetMe)
auth.GET("/posts", postH.List)
auth.POST("/posts", postH.Create)
auth.GET("/posts/:id", postH.Get)
auth.PUT("/posts/:id", postH.Update)
auth.DELETE("/posts/:id", postH.Delete)
return e
}JWT認証ミドルウェアとユーザーハンドラー
Antigravityに「sqlcのGetUserByEmailクエリを使ってJWTログインハンドラーを書いて。bcryptでパスワード検証し、JWTを返す」と依頼すると、以下のようなコードが生成されます。
// internal/api/handler/user_handler.go (抜粋)
func (h *UserHandler) Login(c echo.Context) error {
var req LoginRequest
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "リクエスト形式が不正です")
}
if err := c.Validate(req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// DBからユーザー取得(sqlc生成コード)
user, err := h.queries.GetUserByEmail(c.Request().Context(), req.Email)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "認証情報が正しくありません")
}
// bcryptパスワード検証
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "認証情報が正しくありません")
}
// JWTトークン生成
claims := &JWTClaims{
UserID: user.ID.String(),
Username: user.Username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signed, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "トークン生成に失敗しました")
}
return c.JSON(http.StatusOK, LoginResponse{Token: signed})
}Antigravity活用ポイント: AGENTS.md にsqlcの型情報が記述されているため、ハンドラーコードを生成する際にも型ミスが起きません。GetUserByEmail の戻り値型 db.User の各フィールドを正確に参照したコードが生成されます。
バリデーションとエラーハンドリング
カスタムバリデーター
// internal/api/validator.go
package api
import (
"net/http"
"github.com/go-playground/validator/v10"
"github.com/labstack/echo/v4"
)
type CustomValidator struct {
validator *validator.Validate
}
func NewCustomValidator() *CustomValidator {
return &CustomValidator{validator: validator.New()}
}
func (cv *CustomValidator) Validate(i interface{}) error {
if err := cv.validator.Struct(i); err != nil {
errs := err.(validator.ValidationErrors)
messages := make([]string, len(errs))
for i, e := range errs {
messages[i] = fmt.Sprintf("%s: %s", e.Field(), e.Tag())
}
return echo.NewHTTPError(http.StatusBadRequest, strings.Join(messages, ", "))
}
return nil
}グローバルエラーハンドラー
// internal/middleware/error_handler.go
func CustomHTTPErrorHandler(err error, c echo.Context) {
code := http.StatusInternalServerError
message := "内部サーバーエラーが発生しました"
var he *echo.HTTPError
if errors.As(err, &he) {
code = he.Code
if msg, ok := he.Message.(string); ok {
message = msg
}
}
// 500系エラーのみ詳細ログ
if code >= 500 {
slog.Error("HTTPエラー", "code", code, "error", err,
"path", c.Request().URL.Path,
"request_id", c.Response().Header().Get(echo.HeaderXRequestID),
)
}
c.JSON(code, map[string]interface{}{
"error": message,
"request_id": c.Response().Header().Get(echo.HeaderXRequestID),
})
}testcontainers-go による統合テスト
Goのテストで最も品質を高める手法が、実際のPostgreSQLを起動した統合テストです。Antigravityに「testcontainersを使ったPostgreSQL統合テストのセットアップコードを生成して」と依頼すると、以下のような堅牢なテスト基盤が生成されます。
// internal/service/user_service_test.go
package service_test
import (
"context"
"testing"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/yourname/myapi/internal/db"
"github.com/yourname/myapi/internal/service"
)
func setupTestDB(t *testing.T) *pgxpool.Pool {
t.Helper()
// 実際のPostgreSQLコンテナを起動
pgContainer, err := postgres.RunContainer(
context.Background(),
testcontainers.WithImage("postgres:16"),
postgres.WithDatabase("testdb"),
postgres.WithUsername("testuser"),
postgres.WithPassword("testpass"),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).
WithStartupTimeout(30*time.Second),
),
)
require.NoError(t, err)
t.Cleanup(func() { pgContainer.Terminate(context.Background()) })
connStr, err := pgContainer.ConnectionString(context.Background(), "sslmode=disable")
require.NoError(t, err)
// マイグレーション実行
m, err := migrate.New("file://../../db/migrations", connStr)
require.NoError(t, err)
require.NoError(t, m.Up())
pool, err := pgxpool.New(context.Background(), connStr)
require.NoError(t, err)
t.Cleanup(pool.Close)
return pool
}
func TestUserService_CreateUser(t *testing.T) {
pool := setupTestDB(t)
svc := service.NewUserService(db.New(pool))
user, err := svc.CreateUser(context.Background(), service.CreateUserParams{
Email: "test@example.com",
Username: "testuser",
Password: "securepassword123",
})
require.NoError(t, err)
require.Equal(t, "test@example.com", user.Email)
require.NotEmpty(t, user.ID)
// パスワードはハッシュ化されている
require.NotEqual(t, "securepassword123", user.Password)
}テスト実行(初回はDockerイメージをプルするため2〜3分かかります):
# テスト実行(期待出力)
$ go test ./... -v -count=1
--- PASS: TestUserService_CreateUser (2.34s)
--- PASS: TestUserService_GetUserByEmail (0.12s)
--- PASS: TestPostService_CreateAndPublish (0.89s)
PASS
ok github.com/yourname/myapi/internal/service 3.35sCloud Run へのデプロイ
Dockerfile(マルチステージビルド)
# syntax=docker/dockerfile:1
FROM golang:1.22-alpine AS builder
WORKDIR /app
# 依存関係キャッシュ(レイヤー最適化)
COPY go.mod go.sum ./
RUN go mod download
# ビルド(CGO無効化でシングルバイナリ生成)
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w" \
-o /api ./cmd/server
# 実行イメージ(distroless で最小化・セキュリティ強化)
FROM gcr.io/distroless/static-debian12
COPY --from=builder /api /api
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/api"]Cloud Run デプロイコマンド
# Artifact Registryへプッシュ
gcloud auth configure-docker asia-northeast1-docker.pkg.dev
docker build -t asia-northeast1-docker.pkg.dev/PROJECT_ID/myapi/server:latest .
docker push asia-northeast1-docker.pkg.dev/PROJECT_ID/myapi/server:latest
# Cloud Run にデプロイ(ゼロダウンタイム)
gcloud run deploy myapi \
--image asia-northeast1-docker.pkg.dev/PROJECT_ID/myapi/server:latest \
--region asia-northeast1 \
--platform managed \
--allow-unauthenticated \
--min-instances 1 \
--max-instances 10 \
--concurrency 100 \
--set-env-vars "DATABASE_URL=postgres://...,JWT_SECRET=YOUR_JWT_SECRET"GitHub Actions による CI/CD の自動化
Antigravity Agentに「Cloud Runへの自動デプロイ GitHub Actions ワークフローを作成して」と依頼すると、テスト→ビルド→デプロイの完全なCI/CDパイプラインが生成されます。詳細はGitHub Actions CI/CDパイプライン構築の詳細ガイドを参照してください。
パフォーマンス最適化:Antigravity によるボトルネック分析
プロファイリングのセットアップ
// main.go にpprofを追加(本番では認証でガード)
import _ "net/http/pprof"
go func() {
slog.Info("pprofサーバー起動", "addr", "localhost:6060")
http.ListenAndServe("localhost:6060", nil)
}()# CPUプロファイル取得
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# Antigravityに解析依頼
# 「このプロファイル結果を見て、ボトルネックとなっている関数を特定して改善提案して」DBクエリ最適化のAntigravity活用
sqlcのクエリファイルをAntigravityのコンテキストに含めると、「このクエリにINDEXを追加すると効果的な理由と、追加すべきINDEXを提案して」のような依頼に対して、実際のクエリプランを考慮した的確な提案が返ってきます。
-- Antigravityが提案した最適化例(実行計画をもとに)
EXPLAIN ANALYZE
SELECT p.* FROM posts p
WHERE p.user_id = $1 AND p.published = true
ORDER BY p.created_at DESC;
-- 複合インデックスの追加
CREATE INDEX CONCURRENTLY idx_posts_user_published
ON posts (user_id, published, created_at DESC);
-- 実行時間: 120ms → 3ms(97.5%改善)よくあるエラーと Antigravity を使った対処法
エラー1: pgx: cannot convert type unknown エラー
sqlcが生成したコードでUUID型を正しく扱えない場合に発生します。
# Antigravityに「pgx UUID型の扱い方を修正して」と依頼
# → pgtype.UUID型への変換コードが自動生成されるエラー2: context deadline exceeded — DB接続タイムアウト
pgxpoolの設定値が低すぎる、または同時接続数の上限に達している場合に発生します。
config, _ := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
config.MaxConns = 25 // Cloud Runのmax-instancesに合わせる
config.MinConns = 5 // ウォームアップ済み接続を維持
config.MaxConnLifetime = 30 * time.Minute
config.MaxConnIdleTime = 5 * time.Minuteエラー3: Docker distrolessイメージで exec format error
ローカルがARM Mac(M1/M2/M3/M4)で、Cloud RunはAMD64の場合に発生します。
# マルチプラットフォームビルド
docker buildx build \
--platform linux/amd64 \
-t asia-northeast1-docker.pkg.dev/PROJECT_ID/myapi/server:latest \
--push .まとめ
ここではAntigravityとGoの組み合わせで本番品質のRESTful APIを構築するための完全なワークフローを解説しました。
重要ポイントを振り返ります。
AGENTS.mdの整備がAntigravityのコード生成品質を大幅に向上させる- sqlc × pgx の組み合わせが型安全・高性能なDBアクセスを実現する
- testcontainers-go による統合テストで、モックでは見つからない本番レベルの品質を確保できる
- distrolessイメージ × Cloud Run で、安全かつスケーラブルな本番環境が構築できる
- AntigravityのAgent機能は、ボイラープレート生成だけでなく、設計判断・デバッグ・最適化の相談相手としても機能する
関連する上級リソースとして、Kubernetes × コンテナオーケストレーション実践ガイドやクリーンアーキテクチャ × DDD実装ガイドも合わせて参照することで、より堅牢なバックエンドシステムを設計できます。
Goバックエンド開発