Top Related Projects
Quick Overview
graph-gophers/graphql-go is a GraphQL server library for Go (Golang). It provides a robust implementation of the GraphQL specification, allowing developers to create efficient and type-safe GraphQL APIs in Go. The library focuses on performance and ease of use, making it a popular choice for building GraphQL servers in Go applications.
Pros
- Strong type safety and compile-time checks
- High performance and efficient execution
- Support for GraphQL subscriptions
- Extensive test coverage and active maintenance
Cons
- Steeper learning curve compared to some other GraphQL libraries
- Limited built-in tooling for schema generation from Go types
- Lack of built-in support for automatic persisted queries
- Some advanced features require additional setup or external packages
Code Examples
- Defining a simple GraphQL schema:
type query struct{}
func (_ *query) Hello() string {
return "Hello, world!"
}
schema := graphql.MustParseSchema(`
type Query {
hello: String!
}
`, &query{})
- Executing a GraphQL query:
query := `
query {
hello
}
`
result := graphql.Do(context.Background(), schema, query, nil)
fmt.Println(result)
- Using resolvers with arguments:
type query struct{}
func (_ *query) User(args struct{ ID int32 }) *User {
return &User{ID: args.ID, Name: "John Doe"}
}
type User struct {
ID int32
Name string
}
schema := graphql.MustParseSchema(`
type Query {
user(id: Int!): User
}
type User {
id: Int!
name: String!
}
`, &query{})
Getting Started
To use graph-gophers/graphql-go in your Go project:
-
Install the library:
go get github.com/graph-gophers/graphql-go
-
Import the package in your Go code:
import "github.com/graph-gophers/graphql-go"
-
Define your schema and resolvers, then create a GraphQL handler:
schema := graphql.MustParseSchema(schemaString, &rootResolver{}) http.Handle("/graphql", &relay.Handler{Schema: schema})
-
Start your HTTP server:
log.Fatal(http.ListenAndServe(":8080", nil))
Competitor Comparisons
go generate based graphql server library
Pros of gqlgen
- Code generation approach leads to type-safe, efficient code
- Supports plugins for customizing code generation
- Integrates well with existing Go code and idioms
Cons of gqlgen
- Steeper learning curve due to code generation concepts
- Requires additional build step for code generation
- May produce more boilerplate code in some cases
Code Comparison
gqlgen:
type Query struct {
TodoResolver
}
type TodoResolver struct {
todos []*Todo
}
func (r *TodoResolver) Todos() []*Todo {
return r.todos
}
graphql-go:
var Schema = `
type Query {
todos: [Todo]
}
type Todo {
id: ID!
text: String!
done: Boolean!
}
`
type query struct{}
func (q *query) Todos() []*Todo {
return todos
}
Key Differences
- gqlgen uses code generation to create type-safe resolvers
- graphql-go uses runtime reflection and a schema string
- gqlgen provides more compile-time safety and IDE support
- graphql-go has a simpler setup process for small projects
Both libraries are well-maintained and suitable for production use, with the choice depending on project requirements and developer preferences.
⚡️ A Go framework for rapidly building powerful graphql services
Pros of Thunder
- Supports real-time subscriptions out of the box
- Offers automatic batching and caching of queries
- Provides a simpler API for schema definition and resolvers
Cons of Thunder
- Less mature and less widely adopted compared to graphql-go
- May have fewer community-contributed extensions and tools
- Documentation is not as comprehensive as graphql-go
Code Comparison
Thunder schema definition:
type User struct {
Name string
}
schema.Object("User", User{},
schema.Field("name", &schema.Field{Type: schema.String}),
)
graphql-go schema definition:
var userType = graphql.NewObject(graphql.ObjectConfig{
Name: "User",
Fields: graphql.Fields{
"name": &graphql.Field{Type: graphql.String},
},
})
Thunder offers a more concise and Go-idiomatic way of defining schemas, while graphql-go follows a structure closer to the GraphQL specification. Thunder's approach may be more intuitive for Go developers, but graphql-go's approach might be more familiar to those coming from other GraphQL implementations.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
graphql-go
The goal of this project is to provide full support of the October 2021 GraphQL specification with a set of idiomatic, easy to use Go packages.
While still under development (internal
APIs are almost certainly subject to change), this library is safe for production use.
Features
- minimal API
- support for
context.Context
- support for the
OpenTelemetry
andOpenTracing
standards - schema type-checking against resolvers
- resolvers are matched to the schema based on method sets (can resolve a GraphQL schema with a Go interface or Go struct).
- handles panics in resolvers
- parallel execution of resolvers
- subscriptions
- directive visitors on fields (the API is subject to change in future versions)
(Some) Documentation
Getting started
In order to run a simple GraphQL server locally create a main.go
file with the following content:
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))
}
Then run the file with go run main.go
. To test:
curl -XPOST -d '{"query": "{ hello }"}' localhost:8080/query
For more realistic usecases check our examples section.
Resolvers
A resolver must have one method or field for each field of the GraphQL type it resolves. The method or field name has to be exported and match the schema's field's name in a non-case-sensitive way.
You can use struct fields as resolvers by using SchemaOpt: UseFieldResolvers()
. For example,
opts := []graphql.SchemaOpt{graphql.UseFieldResolvers()}
schema := graphql.MustParseSchema(s, &query{}, opts...)
When using UseFieldResolvers
schema option, a struct field will be used only when:
- there is no method for a struct field
- a struct field does not implement an interface method
- a struct field does not have arguments
The method has up to two arguments:
- Optional
context.Context
argument. - Mandatory
*struct { ... }
argument if the corresponding GraphQL field has arguments. The names of the struct fields have to be exported and have to match the names of the GraphQL arguments in a non-case-sensitive way.
The method has up to two results:
- The GraphQL field's value as determined by the resolver.
- Optional
error
result.
Example for a simple resolver method:
func (r *helloWorldResolver) Hello() string {
return "Hello world!"
}
The following signature is also allowed:
func (r *helloWorldResolver) Hello(ctx context.Context) (string, error) {
return "Hello world!", nil
}
Separate resolvers for different operations
NOTE: This feature is not in the stable release yet. In order to use it you need to run
go get github.com/graph-gophers/graphql-go@master
and in yourgo.mod
file you will have something like:v1.5.1-0.20230216224648-5aa631d05992
It is expected to be released in
v1.6.0
soon.
The GraphQL specification allows for fields with the same name defined in different query types. For example, the schema below is a valid schema definition:
schema {
query: Query
mutation: Mutation
}
type Query {
hello: String!
}
type Mutation {
hello: String!
}
The above schema would result in name collision if we use a single resolver struct because fields from both operations correspond to methods in the root resolver (the same Go struct). In order to resolve this issue, the library allows resolvers for query, mutation and subscription operations to be separated using the Query
, Mutation
and Subscription
methods of the root resolver. These special methods are optional and if defined return the resolver for each opeartion. For example, the following is a resolver corresponding to the schema definition above. Note that there is a field named hello
in both the query and the mutation definitions:
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 Options
UseStringDescriptions()
enables the usage of double quoted and triple quoted. When this is not enabled, comments are parsed as descriptions instead.UseFieldResolvers()
specifies whether to use struct field resolvers.MaxDepth(n int)
specifies the maximum field nesting depth in a query. The default is 0 which disables max depth checking.MaxParallelism(n int)
specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10.Tracer(tracer trace.Tracer)
is used to trace queries and fields. It defaults tonoop.Tracer
.Logger(logger log.Logger)
is used to log panics during query execution. It defaults toexec.DefaultLogger
.PanicHandler(panicHandler errors.PanicHandler)
is used to transform panics into errors during query execution. It defaults toerrors.DefaultPanicHandler
.DisableIntrospection()
disables introspection queries.DirectiveVisitors()
adds directive visitor implementations to the schema. See examples/directives/authorization for an example.
Custom Errors
Errors returned by resolvers can include custom extensions by implementing the ResolverError
interface:
type ResolverError interface {
error
Extensions() map[string]interface{}
}
Example of a simple custom error:
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]interface{} {
return map[string]interface{}{
"code": e.Code,
"message": e.Message,
}
}
Which could produce a GraphQL error such as:
{
"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
By default the library uses noop.Tracer
. If you want to change that you can use the OpenTelemetry or the OpenTracing implementations, respectively:
// 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()))
// ...
Alternatively you can pass an existing trace.Tracer instance:
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{}))
// ...
If you need to implement a custom tracer the library would accept any tracer which implements the interface below:
type Tracer interface {
TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, func([]*errors.QueryError))
TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, func(*errors.QueryError))
TraceValidation(context.Context) func([]*errors.QueryError)
}
Examples
Top Related Projects
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot