Graphql go - Golang learning step 8-3
- 公開日
- カテゴリ:APIClients
- タグ:Golang,roadmap.sh,学習メモ
roadmap.sh > Go > API Clients > GraphQL > Graphql go の学習を進めていきます。
※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。
contents
開発環境
- チップ: Apple M2 Pro
- OS: macOS Sonoma
- go version: go1.23.2 darwin/arm64
参考 URL
- [OpenSource] graphql
- [article] Graphql-go homepage
- [article] Graphql-go documentation
- [video] GraphQL-Go - Golang Tutorial (by TechPractice on YouTube)
- [feed] Explore top posts about GraphQL
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 の処理の流れ
- GET の場合、query パラメータからクエリを取得
- POST の場合、リクエストボディの JSON をパースして query を取得
graphql.Do(graphql.Params{ ... })
を実行してクエリを処理- 結果を 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
のエラーを適切に処理し、問題発生時にログを出力することが重要。GET
とPOST
の両方のリクエストを受け付けるgraphqlHandler
を実装することで柔軟な API 設計が可能。- スキーマは
initSchema()
で一度だけ作成し、グローバル変数として再利用することが推奨される。 curl
だけでなくGraphiQL
を活用することで開発効率が向上。context.Context
を利用すると、リクエストのキャンセルやタイムアウト処理が可能。
[Prev] Step 8-2: Grequests