graph-gophers/graphql-go

GitHub: graph-gophers/graphql-go

一款专注于易用性的 Go 语言 GraphQL 服务器库,全面支持最新 GraphQL 规范并提供丰富的性能优化与可观测性功能。

Stars: 4758 | Forks: 494

# graphql-go [![Sourcegraph](https://sourcegraph.com/github.com/graph-gophers/graphql-go/-/badge.svg)](https://sourcegraph.com/github.com/graph-gophers/graphql-go?badge) [![Go](https://static.pigsec.cn/wp-content/uploads/repos/cas/31/31c96a8fe552af584b5983afc39ddf2e71ca6806185aad0eda564c77331e5b3e.svg)](https://github.com/graph-gophers/graphql-go/actions/workflows/go.yml) [![Go Report](https://goreportcard.com/badge/github.com/graph-gophers/graphql-go)](https://goreportcard.com/report/github.com/graph-gophers/graphql-go) [![GoDoc](https://godoc.org/github.com/graph-gophers/graphql-go?status.svg)](https://godoc.org/github.com/graph-gophers/graphql-go)

本项目的目标是提供对 [2025年9月 GraphQL 规范](https://spec.graphql.org/September2025/) 的全面支持,并提供一套地道、易用的 Go 包。 ## 功能 - 极简 API - 支持 `context.Context` - 支持 `OpenTelemetry` 和 `OpenTracing` 标准 - 针对 resolver 的 schema 类型检查 - 根据方法集将 resolver 与 schema 进行匹配(可以通过 Go interface 或 Go struct 解析 GraphQL schema)。 - 处理 resolver 中的 panic - 并行执行 resolver - 检查选定的字段及其参数,以预取数据并避免 N+1 查询问题 - subscriptions - [WS transport 示例](https://github.com/graph-gophers/graphql-transport-ws) ## (部分)文档 [![GoDoc](https://godoc.org/github.com/graph-gophers/graphql-go?status.svg)](https://godoc.org/github.com/graph-gophers/graphql-go) ### 快速开始 要在本地运行一个简单的 GraphQL 服务器,请创建一个包含以下内容的 `main.go` 文件: ``` package main import ( "log" "net/http" graphql "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" ) type query struct{} func (query) Hello() string { return "Hello, world!" } func main() { s := ` type Query { hello: String! } ` schema := graphql.MustParseSchema(s, &query{}) http.Handle("/query", &relay.Handler{Schema: schema}) log.Fatal(http.ListenAndServe(":8080", nil)) } ``` 然后使用 `go run main.go` 运行该文件。测试如下: ``` curl -XPOST -d '{"query": "{ hello }"}' localhost:8080/query ``` 有关更真实的用例,请查看我们的[示例部分](https://github.com/graph-gophers/graphql-go/wiki/Examples)。 ### Resolver resolver 必须为其解析的 GraphQL 类型的每个字段提供一个方法或字段。方法或字段名称必须是[可导出的](https://golang.org/ref/spec#Exported_identifiers),并且以不区分大小写的方式与 schema 字段的名称相匹配。 你可以通过使用 `SchemaOpt: UseFieldResolvers()` 将 struct 字段用作 resolver。例如, ``` opts := []graphql.SchemaOpt{graphql.UseFieldResolvers()} schema := graphql.MustParseSchema(s, &query{}, opts...) ``` 当使用 `UseFieldResolvers` schema 选项时,*仅*在以下情况会使用 struct 字段: - struct 字段没有对应的方法 - struct 字段未实现 interface 方法 - struct 字段没有参数 该方法最多有两个参数: - 可选的 `context.Context` 参数。 - 如果对应的 GraphQL 字段有参数,则需要必填的 `*struct { ... }` 参数。struct 字段的名称必须是[可导出的](https://golang.org/ref/spec#Exported_identifiers),并且必须以不区分大小写的方式与 GraphQL 参数的名称相匹配。 该方法最多有两个返回值: - 由 resolver 确定的 GraphQL 字段的值。 - 可选的 `error` 返回值。 简单 resolver 方法的示例: ``` func (r *helloWorldResolver) Hello() string { return "Hello world!" } ``` 也允许使用以下签名: ``` func (r *helloWorldResolver) Hello(ctx context.Context) (string, error) { return "Hello world!", nil } ``` ### 针对不同操作的独立 resolver 此功能在 `v1.6.0` 版本中发布。 GraphQL 规范允许在不同的 query 类型中定义具有相同名称的字段。例如,以下 schema 是一个有效的 schema 定义: ``` schema { query: Query mutation: Mutation } type Query { hello: String! } type Mutation { hello: String! } ``` 如果我们使用单个 resolver struct,上述 schema 将导致名称冲突,因为两个操作中的字段都对应于根 resolver(同一个 Go struct)中的方法。为了解决这个问题,本库允许使用根 resolver 的 `Query`、`Mutation` 和 `Subscription` 方法将 query、mutation 和 subscription 操作的 resolver 分离开来。这些特殊方法是可选的,如果定义了,它们将返回各自操作的 resolver。例如,以下是与上述 schema 定义相对应的 resolver。请注意,在 query 和 mutation 定义中都有一个名为 `hello` 的字段: ``` type RootResolver struct{} type QueryResolver struct{} type MutationResolver struct{} func(r *RootResolver) Query() *QueryResolver { return &QueryResolver{} } func(r *RootResolver) Mutation() *MutationResolver { return &MutationResolver{} } func (*QueryResolver) Hello() string { return "Hello query!" } func (*MutationResolver) Hello() string { return "Hello mutation!" } schema := graphql.MustParseSchema(sdl, &RootResolver{}, nil) ... ``` ### Schema 选项 - `UseStringDescriptions()` 启用 schema/类型系统描述字符串(双引号和三引号)。未启用此选项时,会将 schema 注释解析为描述。 - `UseFieldResolvers()` 指定是否使用 struct 字段 resolver。 - `MaxDepth(n int)` 指定 query 中的最大字段嵌套深度。默认值为 0,即禁用最大深度检查。 - `MaxParallelism(n int)` 指定每个请求允许并行运行的 resolver 最大数量。默认值为 10。 - `MaxPooledBufferCap(n int)` 指定内部内存池中存储的 buffer 的最大容量。默认为 16KB。超过此限制的 buffer 将被丢弃而不会进入池中。 - `Tracer(tracer trace.Tracer)` 用于追踪 query 和字段。默认为 `noop.Tracer`。 - `Logger(logger log.Logger)` 用于记录 query 执行期间的 panic。默认为 `exec.DefaultLogger`。 - `PanicHandler(panicHandler errors.PanicHandler)` 用于在 query 执行期间将 panic 转换为 error。默认为 `errors.DefaultPanicHandler`。 - `DisableIntrospection()` 禁用 introspection query。 - `DisableFieldSelections()` 禁用捕获辅助 API 使用的子字段选择(见下文)。 - `DisableMemoryPooling()` 禁用内部执行路径的内存池。默认情况下内存池是启用的;此选项用于诊断和基准比较。 - `OverlapValidationLimit(n int)` 设置校验期间检查的重叠对数量的硬性上限;超过此限制会发出 `OverlapValidationLimitExceeded` 错误。 ### 字段选择检查辅助工具 resolver 可以使用以下方法 introspect 请求的直接子字段: ``` graphql.SelectedFieldNames(ctx) // []string of direct child schema field names graphql.HasSelectedField(ctx, "name") // bool graphql.SortedSelectedFieldNames(ctx) // sorted copy ``` 用例包括为数据库构建投影列表,或有条件地避免昂贵的子查询。这些辅助工具是有意设计为浅层的(仅限直接子节点),fragment spread / inline fragment 会被展平并移除重复项;元字段(例如 `__typename`)会被排除。 性能:仅当调用辅助工具时,才会延迟计算选择数据。如果你从不调用它们,实际上不会有任何额外的开销。为了移除即使是微小的 context 值插入操作,你可以使用 `DisableFieldSelections()` 选择退出;之后辅助工具将返回空结果。 有关更多详细信息和示例,请参阅[文档](https://godoc.org/github.com/graph-gophers/graphql-go)。 ### 自定义错误 resolver 返回的错误可以通过实现 `ResolverError` interface 来包含自定义扩展: ``` type ResolverError interface { error Extensions() map[string]any } ``` 简单自定义错误的示例: ``` type droidNotFoundError struct { Code string `json:"code"` Message string `json:"message"` } func (e droidNotFoundError) Error() string { return fmt.Sprintf("error [%s]: %s", e.Code, e.Message) } func (e droidNotFoundError) Extensions() map[string]any { return map[string]any{ "code": e.Code, "message": e.Message, } } ``` 这可能会产生如下 GraphQL 错误: ``` { "errors": [ { "message": "error [NotFound]: This is not the droid you are looking for", "path": [ "droid" ], "extensions": { "code": "NotFound", "message": "This is not the droid you are looking for" } } ], "data": null } ``` ### Tracing 默认情况下,本库使用 `noop.Tracer`。如果你想更改此项,可以分别使用 OpenTelemetry 或 OpenTracing 实现: ``` // OpenTelemetry tracer package main import ( "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/starwars" otelgraphql "github.com/graph-gophers/graphql-go/trace/otel" "github.com/graph-gophers/graphql-go/trace/tracer" ) // ... _, err := graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(otelgraphql.DefaultTracer())) // ... ``` 或者,你可以传入一个现有的 `trace.Tracer` 实例: ``` tr := otel.Tracer("example") _, err = graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(&otelgraphql.Tracer{Tracer: tr})) ``` ``` // OpenTracing tracer package main import ( "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/example/starwars" "github.com/graph-gophers/graphql-go/trace/opentracing" "github.com/graph-gophers/graphql-go/trace/tracer" ) // ... _, err := graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(opentracing.Tracer{})) // ... ``` 如果你需要实现自定义 tracer,本库将接受任何实现了以下 interface 的 tracer: ``` type Tracer interface { TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]any, varTypes map[string]*introspection.Type) (context.Context, func([]*errors.QueryError)) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]any) (context.Context, func(*errors.QueryError)) TraceValidation(context.Context) func([]*errors.QueryError) } ``` ### [示例](https://github.com/graph-gophers/graphql-go/wiki/Examples)
标签:API开发, EVTX分析, Go, GraphQL, Ruby工具, SOC Prime, Syscall, Web开发, 开发工具, 日志审计, 服务端, 用户代理