Go gqlgenのAPQを試す
APQ(Automatic persisted queries)を使ったサーバーを実装し、GraphQLクエリ登録の挙動を試す
Go gqlgenのAPQを試す
APQ(Automatic persisted queries)を使ったサーバーを実装し、GraphQLクエリ登録の挙動を試す
環境と各バージョン
- M1 Mac
- go 1.23.2
- github.com/99designs/gqlgen v0.17.49
実装
GraphQL定義
type Todo {
id: ID!
text: String!
done: Boolean!
}
type Query {
todo(id: ID!): Todo!
}
サーバーの実装 handler.NewDefaultServerを使えば 、キャッシュストアにhashicorp/golang-lruを使ったAPQが有効になる だが今回は自前のStructをキャッシュストアを使う またHTTPキャッシュを想定するため、GETメソッドを使うべくtransport.GETを使う
package main
import (
"context"
"gra/graph"
"log"
"net/http"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/handler/extension"
"github.com/99designs/gqlgen/graphql/handler/transport"
)
type cache struct {
s map[string]any
}
func (c *cache) Get(ctx context.Context, key string) (value any, ok bool) {
if v, ok := c.s[key]; ok {
return v, true
}
return nil, false
}
func (c *cache) Add(ctx context.Context, key string, value any) {
c.s[key] = value
}
func main() {
srv := handler.New(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}}))
srv.AddTransport(transport.GET{})
c := &cache{
s: make(map[string]any),
}
srv.Use(extension.AutomaticPersistedQuery{Cache: c})
http.Handle("/query", srv)
log.Fatal(http.ListenAndServe(":8080", nil))
}
resolverの実装
package graph
import (
"context"
"fmt"
"gra/graph/model"
)
func (r *queryResolver) Todo(ctx context.Context, id string) (*model.Todo, error) {
t := map[string]*model.Todo{
"1": {ID: "1", Text: "洗濯する", Done: false},
"2": {ID: "2", Text: "料理する", Done: true},
}
if todo, ok := t[id]; ok {
return todo, nil
}
return nil, fmt.Errorf("todo not found")
}
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
type queryResolver struct{ *Resolver }
リクエストする
リクエストしたいGraphQLクエリをSHA-256で変換する
echo -n 'query findTodo($id: ID!) { todo(id: $id) { text } }' | shasum -a 256
まずはpersistedQueryのみでリクエストする
curl --get http://localhost:8080/query \
--header 'content-type: application/json' \
--data-urlencode 'extensions={"persistedQuery":{"version":1,"sha256Hash":"753d19484462e54ceed5af058741e0d4c57d20eca3d80bbe58d92bfa1b6cec4d"}}' \
--data-urlencode 'variables={"id": "1"}'
まだGraphQLクエリを登録してないのでエラーが返る
{"errors":[{"message":"PersistedQueryNotFound","extensions":{"code":"PERSISTED_QUERY_NOT_FOUND"}}],"data":null}
GraphQLクエリを加えてリクエストする
curl --get http://localhost:8080/query \
--header 'content-type: application/json' \
--data-urlencode 'extensions={"persistedQuery":{"version":1,"sha256Hash":"753d19484462e54ceed5af058741e0d4c57d20eca3d80bbe58d92bfa1b6cec4d"}}' \
--data-urlencode 'query=query findTodo($id: ID!) { todo(id: $id) { text } }' \
--data-urlencode 'variables={"id": "1"}'
結果が返る
{"data":{"todo":{"text":"洗濯する"}}}
再度persistedQueryのみでリクエストすると同じ結果が返る
またvariablesの値を変えても期待した結果が返る
curl --get http://localhost:8080/query \
--header 'content-type: application/json' \
--data-urlencode 'extensions={"persistedQuery":{"version":1,"sha256Hash":"753d19484462e54ceed5af058741e0d4c57d20eca3d80bbe58d92bfa1b6cec4d"}}' \
--data-urlencode 'variables={"id": "2"}'
{"data":{"todo":{"text":"料理する"}}}
クライアントのGraphQLクエリ登録のタイミング
gqlgenの公式リファレンスに記載の通り、まずはPersistedQueryのみでリクエストし、PersistedQueryNotFoundが返ってきたらGraphQLクエリを含めてリクエストすることでクエリ登録する
公式リファレンスではキャッシュストアにRedisを使っているが、わざわざRedisを使わずともlruで良いと思う
所感
導入にあたっては特にクライアントの改修が必要になり、クライアントの複雑さが増す APQはネットワーク転送量削減やCDNキャッシュ促進のモチベーションで使う 特にGraphQLの利点を存分に生かしてないにも関わらずAPQを使うくらいであれば、もはやREST APIにしたほうがいいかもしれない
메타데이터
- post_id
- fcbeeb2aeec9
- slug
- go-gqlgenのapqを試す-fcbeeb2aeec9
- url
- https://medium.com/@eyasy1217/go-gqlgen%E3%81%AEapq%E3%82%92%E8%A9%A6%E3%81%99-fcbeeb2aeec9
- canonical_url
- https://medium.com/@eyasy1217/go-gqlgen%E3%81%AEapq%E3%82%92%E8%A9%A6%E3%81%99-fcbeeb2aeec9
- author_url
- https://medium.com/@eyasy1217
- status
- ok
- fetched_at
- 2026-07-23 14:04:14