1. Home
  2. Golang
  3. GuideToBecomingGoDeveloper
  4. APIClients
  5. Graphql go - Golang learning step 8-3

Graphql go - Golang learning step 8-3

  • 公開日
  • カテゴリ:APIClients
  • タグ:Golang,roadmap.sh,学習メモ
Graphql go - Golang learning step 8-3

roadmap.sh > Go > API Clients > GraphQL > Graphql go の学習を進めていきます。

※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. 参考 URL
  3. Graphql go
    1. graphql-go/graphql の特徴
  4. インストール
  5. サーバーのコードを作成
    1. 1. User 型の定義
    2. 2. データソース(サンプル)
    3. 3. クエリの定義
    4. 4. スキーマの作成
    5. 5. GraphQL サーバーの起動
  6. サーバーの起動
  7. クエリの実行
  8. POST のエンドポイントを追加する
    1. 1. もともとの GET のみの実装
    2. 2. POST に対応するための graphqlHandler の追加
    3. 3. http.HandleFunc() のクロージャを graphqlHandler に置き換え
    4. POST リクエストの実行

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

参考 URL

Graphql go

graphql-go/graphql は、Go で GraphQL サーバーを構築するためのライブラリです。このライブラリを使用すると、Go のコード内でスキーマを定義し、GraphQL クエリを処理できます。

graphql-go/graphql の特徴

  • Go で GraphQL サーバーを実装可能
  • スキーマを Go のコード内で定義できる
  • クエリとミューテーションの処理をカスタマイズ可能
  • net/http と組み合わせて HTTP サーバーを構築可能

インストール

パッケージをインストールします。

go get github.com/graphql-go/graphql

サーバーのコードを作成

次に、サーバーのコードを書きます。以下のコードを server.go を作成し保存します。

package main

import (
  "encoding/json"
  "fmt"
  "log"
  "net/http"

  "github.com/graphql-go/graphql"
)

// 1. User 型の定義
var userType = graphql.NewObject(
  graphql.ObjectConfig{
    Name: "User",
    Fields: graphql.Fields{
      "id": &graphql.Field{Type: graphql.Int},
      "name": &graphql.Field{Type: graphql.String},
      "age": &graphql.Field{Type: graphql.Int},
    },
  },
)

// 2. データソース(サンプル)
var users = []map[string]interface{}{
  {"id": 1, "name": "Alice", "age": 30},
  {"id": 2, "name": "Bob", "age": 25},
}

// 3. クエリの定義
var rootQuery = graphql.NewObject(
  graphql.ObjectConfig{
    Name: "RootQuery",
    Fields: graphql.Fields{
      "user": &graphql.Field{
        Type: userType,
        Args: graphql.FieldConfigArgument{
          "id": &graphql.ArgumentConfig{Type: graphql.Int},
        },
        Resolve: func(p graphql.ResolveParams) (interface{}, error) {
          idFloat, ok := p.Args["id"].(float64)
          if !ok {
            return nil, nil
          }
          id := int(idFloat)
          
          for _, user := range users {
            if user["id"] == id {
              return user, nil
            }
          }
          return nil, nil
        },
      },
    },
  },
)

// 4. スキーマの作成
func initSchema() (graphql.Schema, error) {
	var schema graphql.Schema
	var err error

	schema, err = graphql.NewSchema(
		graphql.SchemaConfig{
			Query: rootQuery,
		},
	)
	if err != nil {
		return graphql.Schema{}, fmt.Errorf("❌ Failed to create GraphQL schema: %v", err)
	}

	return schema, nil
}

// 5. GraphQL サーバーの起動
func main() {
  schema, err = initSchema()
  if err != nil {
    log.Fatalf("❌ Failed to init schema: %v", err)
  }
  http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
    query := r.URL.Query().Get("query")
    result := graphql.Do(graphql.Params{
      Schema:        schema,
      RequestString: query,
    })
    json.NewEncoder(w).Encode(result)
  })

  port := 8080
  fmt.Printf("🚀 Server is running at http://localhost:%d/graphql\n", port)
  log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}

1. User 型の定義

GraphQL では、取得するデータの構造(スキーマ)を Go の struct ではなく graphql.NewObject を使って定義します。

var userType = graphql.NewObject(
  graphql.ObjectConfig{
    Name: "User",
    Fields: graphql.Fields{
      "id":   &graphql.Field{Type: graphql.Int},
      "name": &graphql.Field{Type: graphql.String},
      "age":  &graphql.Field{Type: graphql.Int},
    },
  },
)
  • graphql.NewObject(graphql.ObjectConfig{...})
    • Name: "User": GraphQL の型名(データの種類)
    • Fields: graphql.Fields{...}: フィールドの定義
      • id: 整数型 (graphql.Int)
      • name: 文字列 (graphql.String)
      • age: 整数 (graphql.Int)

この userType を使って GraphQL のクエリやレスポンスの形式を定義できます。

2. データソース(サンプル)

データベースがないので、仮のデータを配列として用意します。

var users = []map[string]interface{}{
  {"id": 1, "name": "Alice", "age": 30},
  {"id": 2, "name": "Bob", "age": 25},
}

3. クエリの定義

クエリ(Query)を定義します。 GraphQL のクエリとは、「どのデータを取得するか?」を指定するものです。

var rootQuery = graphql.NewObject(
  graphql.ObjectConfig{
    Name: "RootQuery",
    Fields: graphql.Fields{
      "user": &graphql.Field{
        Type: userType,
        Args: graphql.FieldConfigArgument{
          "id": &graphql.ArgumentConfig{Type: graphql.Int},
        },
        Resolve: func(p graphql.ResolveParams) (interface{}, error) {
          idFloat, ok := p.Args["id"].(float64)
          if !ok {
            return nil, nil
          }
          id := int(idFloat)
          
          for _, user := range users {
            if user["id"] == id {
              return user, nil
            }
          }
          return nil, nil
        },
      },
    },
  },
)
  • rootQuery(ルートクエリ)を作成
    • user: id を指定すると、そのユーザーの情報を取得
  • Resolve 関数が 実際にデータを取得する処理
    • p.Args["id"] から id を取得
    • users の中から id が一致するユーザーを探して返す

4. スキーマの作成

GraphQL スキーマを作成します。

var schema, _ = graphql.NewSchema(
  graphql.SchemaConfig{
    Query: rootQuery,
  },
)
  • graphql.NewSchema(graphql.SchemaConfig{ Query: rootQuery })
    • 先ほど作った rootQuery を GraphQL スキーマに登録
    • クエリを処理できる状態にする

5. GraphQL サーバーの起動

HTTP サーバーを作成します。

func main() {
  http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
    query := r.URL.Query().Get("query")
    result := graphql.Do(graphql.Params{
      Schema:        schema,
      RequestString: query,
    })
    json.NewEncoder(w).Encode(result)
  })

  port := 8080
  fmt.Printf("🚀 Server is running at http://localhost:%d/graphql\n", port)
  log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}

1. HTTP ハンドラを作成

http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { ... })

/graphql エンドポイントにアクセスすると、この関数が実行される

2. クエリを取得

query := r.URL.Query().Get("query")

URL の query パラメータを取得

http://localhost:8080/graphql?query={user(id:1){id,name,age}}

3. GraphQL の処理を実行

result := graphql.Do(graphql.Params{
    Schema:        schema,
    RequestString: query,
})

graphql.Do(...) で GraphQL のクエリを処理 してレスポンスを取得

4. JSON を返す

json.NewEncoder(w).Encode(result)

クライアント(ブラウザや curl)に JSON 形式でレスポンスを送信

5. サーバーを起動

port := 8080
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
  • 8080 ポートで HTTP サーバーを起動
  • http://localhost:8080/graphql にアクセス可能

サーバーの起動

次のコマンドを実行して、サーバーを起動します。

go run server.go

サーバーが起動すると、以下のようなメッセージが表示されます。

🚀 Server is running at http://localhost:8080/graphql

クエリの実行

curl でリクエストを送信すると、レスポンスが返ってきます。

curl "http://localhost:8080/graphql?query=%7Buser(id%3A1)%7Bid%2Cname%2Cage%7D%7D"

レスポンス

{"data":{"user":{"age":30,"id":1,"name":"Alice"}}}

POST のエンドポイントを追加する

GET のみの定義から POST のエンドポイントを追加します。

GET・POST の両方のリクエストを適切に処理する graphqlHandler 関数を server.go に作成し、GraphQL サーバー起動時の http.HandleFunc() の第2引数のクロージャをこれと置き換えます。

package main

import (
  "encoding/json"
  "fmt"
  "io"
  "log"
  "net/http"

  "github.com/graphql-go/graphql"
)

// User 型の定義
var userType = graphql.NewObject(
  graphql.ObjectConfig{
    Name: "User",
    Fields: graphql.Fields{
      "id":   &graphql.Field{Type: graphql.Int},
      "name": &graphql.Field{Type: graphql.String},
      "age":  &graphql.Field{Type: graphql.Int},
    },
  },
)

// データソース(サンプル)
var users = []map[string]interface{}{
  {"id": 1, "name": "Alice", "age": 30},
  {"id": 2, "name": "Bob", "age": 25},
}

// クエリの定義
var rootQuery = graphql.NewObject(
  graphql.ObjectConfig{
    Name: "RootQuery",
    Fields: graphql.Fields{
      "user": &graphql.Field{
        Type: userType,
        Args: graphql.FieldConfigArgument{
          "id": &graphql.ArgumentConfig{Type: graphql.Int},
        },
        Resolve: func(p graphql.ResolveParams) (interface{}, error) {
          id, ok := p.Args["id"].(int)
          if !ok {
            return nil, nil
          }
          for _, user := range users {
            if user["id"] == id {
              return user, nil
            }
          }
          return nil, nil
        },
      },
    },
  },
)

// スキーマの作成
func initSchema() (graphql.Schema, error) {
	var schema graphql.Schema
	var err error

	schema, err = graphql.NewSchema(
		graphql.SchemaConfig{
			Query: rootQuery,
		},
	)
	if err != nil {
		return graphql.Schema{}, fmt.Errorf("❌ Failed to create GraphQL schema: %v", err)
	}

	return schema, nil
}

// GraphQL ハンドラー(GET & POST 両対応)
func graphqlHandler(w http.ResponseWriter, r *http.Request) {
  var query string

  if r.Method == http.MethodGet {
    // GET クエリパラメータから取得
    query = r.URL.Query().Get("query")
  } else if r.Method == http.MethodPost {
    // POST の場合、リクエストボディを読み取る
    body, err := io.ReadAll(r.Body)
    if err != nil {
      http.Error(w, "Failed to read request body", http.StatusInternalServerError)
      return
    }
    defer r.Body.Close()

    var req struct {
      Query string `json:"query"`
    }
    if err := json.Unmarshal(body, &req); err != nil {
      http.Error(w, "Invalid JSON", http.StatusBadRequest)
      return
    }
    query = req.Query
  } else {
    http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
    return
  }

  // GraphQL の実行
  result := graphql.Do(graphql.Params{
    Schema:        schema,
    RequestString: query,
  })

  // JSON レスポンスを返す
  w.Header().Set("Content-Type", "application/json")
  if err := json.NewEncoder(w).Encode(result); err != nil {
    http.Error(w, "Failed to encode JSON response", http.StatusInternalServerError)
  }
}

var schema graphql.Schema

// メイン関数
func main() {
  var err error
  schema, err = initSchema()
  if err != nil {
    log.Fatalf("❌ Failed to init schema: %v", err)
  }
  
  http.HandleFunc("/graphql", graphqlHandler)

  port := 8080
  fmt.Printf("🚀 Server is running at http://localhost:%d/graphql\n", port)
  log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}

1. もともとの GET のみの実装

server.go で GET のみ を処理していたコードでは、以下のように http.HandleFunc() の クロージャ内で直接クエリを処理 していました。

http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
  query := r.URL.Query().Get("query") // クエリを取得(GET のみ対応)
  result := graphql.Do(graphql.Params{
    Schema:        schema,
    RequestString: query,
  })
  json.NewEncoder(w).Encode(result) // JSON レスポンスを返す
})

このコードでは GET メソッドのみ に対応しており、POST のリクエストを受け付けることができません。

2. POST に対応するための graphqlHandler の追加

GET だけでなく POST でもリクエストを処理するために、graphqlHandler 関数を作成 します。

func graphqlHandler(w http.ResponseWriter, r *http.Request) {
  var query string

  if r.Method == http.MethodGet {
    // GET: クエリパラメータから取得
    query = r.URL.Query().Get("query")

  } else if r.Method == http.MethodPost {
    // POST: リクエストボディから JSON を取得
    body, err := io.ReadAll(r.Body)
    if err != nil {
      http.Error(w, "Failed to read request body", http.StatusInternalServerError)
      return
    }
    defer r.Body.Close()

    var req struct {
      Query string `json:"query"`
    }
    if err := json.Unmarshal(body, &req); err != nil {
      http.Error(w, "Invalid JSON", http.StatusBadRequest)
      return
    }
    query = req.Query
  } else {
    // GET/POST 以外はエラー
    http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
    return
  }

  // GraphQL の処理を実行
  result := graphql.Do(graphql.Params{
    Schema:        schema,
    RequestString: query,
  })

  // JSON レスポンスを返す
  w.Header().Set("Content-Type", "application/json")
  if err := json.NewEncoder(w).Encode(result); err != nil {
    http.Error(w, "Failed to encode JSON response", http.StatusInternalServerError)
  }
}

graphqlHandler の処理の流れ

  1. GET の場合、query パラメータからクエリを取得
  2. POST の場合、リクエストボディの JSON をパースして query を取得
  3. graphql.Do(graphql.Params{ ... }) を実行してクエリを処理
  4. 結果を JSON でレスポンスとして返す

3. http.HandleFunc() のクロージャを graphqlHandler に置き換え

この新しい graphqlHandler 関数を サーバー起動時の http.HandleFunc() に登録 します。

func main() {
  // `/graphql` のリクエストをすべて `graphqlHandler` で処理
  http.HandleFunc("/graphql", graphqlHandler)

  port := 8080
  fmt.Printf("🚀 Server is running at http://localhost:%d/graphql\n", port)
  log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}

これで GET/POST の両方に対応した GraphQL エンドポイントが完成します。

POST リクエストの実行

curl を使って POST でクエリを実行します。

curl -X POST "http://localhost:8080/graphql" \
  -H "Content-Type: application/json" \
  -d '{"query": "{ user(id: 1) { id, name, age } }"}'

レスポンス:

{
  "data": {
    "user": {
      "id": 1,
      "name": "Alice",
      "age": 30
    }
  }
}

まとめ

  • graphql-go/graphql は Go で GraphQL サーバーを実装するためのライブラリ。
  • スキーマを Go のコード内で定義可能。
  • クエリとミューテーションの処理をカスタマイズ可能。
  • net/http を使用して簡単に GraphQL サーバーを構築可能。
  • Resolve 関数では p.Args["id"].(float64) とし、適切に型変換を行う必要あり。
  • graphql.NewSchema のエラーを適切に処理し、問題発生時にログを出力することが重要。
  • GETPOST の両方のリクエストを受け付ける graphqlHandler を実装することで柔軟な API 設計が可能。
  • スキーマは initSchema() で一度だけ作成し、グローバル変数として再利用することが推奨される。
  • curl だけでなく GraphiQL を活用することで開発効率が向上。
  • context.Context を利用すると、リクエストのキャンセルやタイムアウト処理が可能。


[Prev] Step 8-2: Grequests

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級