graphql

package module
v1.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 9, 2025 License: BSD-2-Clause Imports: 22 Imported by: 1,311

README

graphql-go Sourcegraph Go Go Report GoDoc

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 and OpenTracing 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
  • inspect the selected fields and their args to prefetch data and avoid the N+1 query problem
  • subscriptions

(Some) Documentation GoDoc

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

This feature was released in v1.6.0.

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 to noop.Tracer.
  • Logger(logger log.Logger) is used to log panics during query execution. It defaults to exec.DefaultLogger.
  • PanicHandler(panicHandler errors.PanicHandler) is used to transform panics into errors during query execution. It defaults to errors.DefaultPanicHandler.
  • DisableIntrospection() disables introspection queries.
  • DisableFieldSelections() disables capturing child field selections used by helper APIs (see below).
  • OverlapValidationLimit(n int) sets a hard cap on examined overlap pairs during validation; exceeding it emits OverlapValidationLimitExceeded error.
Field Selection Inspection Helpers

Resolvers can introspect which immediate child fields were requested using:

graphql.SelectedFieldNames(ctx)       // []string of direct child schema field names
graphql.HasSelectedField(ctx, "name") // bool
graphql.SortedSelectedFieldNames(ctx) // sorted copy

Use cases include building projection lists for databases or conditionally avoiding expensive sub-fetches. The helpers are intentionally shallow (only direct children) and fragment spreads / inline fragments are flattened with duplicates removed; meta fields (e.g. __typename) are excluded.

Performance: selection data is computed lazily only when a helper is called. If you never call them there is effectively no additional overhead. To remove even the small context value insertion you can opt out with DisableFieldSelections(); helpers then return empty results.

For more detail and examples see the docs.

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

Documentation

Overview

Example

Example demonstrates how to parse a GraphQL schema and execute a query against it.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"

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

type exampleResolver struct{}

func (*exampleResolver) Greet(ctx context.Context, args struct{ Name string }) string {
	return fmt.Sprintf("Hello, %s!", args.Name)
}

// Example demonstrates how to parse a GraphQL schema and execute a query against it.
func main() {
	s := `
	  schema {
	    query: Query
	  }
	  
	  type Query {
	    greet(name: String!): String!
	  }
	`
	opts := []graphql.SchemaOpt{
		// schema options go here
	}
	schema := graphql.MustParseSchema(s, &exampleResolver{}, opts...)
	query := `
		query {
			greet(name: "GraphQL")
		}
	`

	res := schema.Exec(context.Background(), query, "", nil)

	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	err := enc.Encode(res)
	if err != nil {
		panic(err)
	}

}
Output:

{
  "data": {
    "greet": "Hello, GraphQL!"
  }
}
Example (CustomErrors)

Example_customErrors demonstrates the use of custom errors and error extensions.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"

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

type product struct {
	ID   graphql.ID
	Name string
}

type custErrResolver struct {
	products map[graphql.ID]*product
}

func (r *custErrResolver) Product(ctx context.Context, args struct{ ID graphql.ID }) (*productResolver, error) {
	if p := r.products[args.ID]; p != nil {
		return &productResolver{p: p}, nil
	}
	traceID := "your-trace-id-here" // get trace ID from ctx
	return nil, &productNotFoundError{Code: "NotFound", Message: "Product not found", TraceID: traceID}
}

type productResolver struct {
	p *product
}

func (r *productResolver) ID() graphql.ID {
	return r.p.ID
}

func (r *productResolver) Name() string {
	return r.p.Name
}

type productNotFoundError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	TraceID string `json:"traceId"`
}

func (e productNotFoundError) Error() string {
	return fmt.Sprintf("error [%s]: %s.", e.Code, e.Message)
}

// Extensions provides additional error context according to the spec https://spec.graphql.org/October2021/#sel-GAPHRPZCAACCBx6b.
func (e productNotFoundError) Extensions() map[string]interface{} {
	return map[string]interface{}{
		"code":    e.Code,
		"message": e.Message,
		"traceId": e.TraceID,
	}
}

// Example_customErrors demonstrates the use of custom errors and error extensions.
func main() {
	products := []*product{
		{ID: "1000", Name: "Product1"},
		{ID: "1001", Name: "Product2"},
	}
	resolver := &custErrResolver{
		products: map[graphql.ID]*product{},
	}
	for _, p := range products {
		resolver.products[p.ID] = p
	}
	s := `
	schema {
		query: Query
	}

	type Query {
		product(id: ID!): Product!
	}

	type Product {
		id: ID!
		name: String!
	}
	`
	schema := graphql.MustParseSchema(s, resolver)

	query := `
	  query {
		product(id: "1007") {
			id
			name
		}
	  }
	`
	res := schema.Exec(context.Background(), query, "", nil)

	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	err := enc.Encode(res)
	if err != nil {
		panic(err)
	}

}
Output:

{
  "errors": [
    {
      "message": "error [NotFound]: Product not found.",
      "path": [
        "product"
      ],
      "extensions": {
        "code": "NotFound",
        "message": "Product not found",
        "traceId": "your-trace-id-here"
      }
    }
  ],
  "data": null
}
Example (CustomScalarMap)
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"

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

type Map map[string]interface{}

func (Map) ImplementsGraphQLType(name string) bool {
	return name == "Map"
}

func (m *Map) UnmarshalGraphQL(input interface{}) error {
	val, ok := input.(map[string]interface{})
	if !ok {
		return fmt.Errorf("wrong type")
	}
	*m = val
	return nil
}

type Args struct {
	Name string
	Data Map
}

type mutation struct{}

func (m *mutation) Name() string {
	return "test"
}

func (*mutation) Hello(args Args) string {
	fmt.Println(args)
	return "Args accepted!"
}

func main() {
	s := `
		scalar Map
	
		type Query {
			name: String!
		}
		
		type Mutation {
			hello(
				name: String!
				data: Map!
			): String!
		}
	`
	schema := graphql.MustParseSchema(s, &mutation{})

	query := `
	  mutation {
		hello(name: "GraphQL", data: {
			num: 5,
			code: "example"
		})
	  }
	`

	res := schema.Exec(context.Background(), query, "", nil)

	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	err := enc.Encode(res)
	if err != nil {
		panic(err)
	}

}
Output:

{GraphQL map[code:example num:5]}
{
  "data": {
    "hello": "Args accepted!"
  }
}
Example (HasSelectedField)

Example_hasSelectedField demonstrates HasSelectedField helper for conditional logic without needing the full slice of field names. This can be handy when checking for a small number of specific fields (avoids allocating the names slice if it hasn't already been built).

package main

import (
	"context"
	"fmt"

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

type (
	user2         struct{ id, name, email string }
	userResolver2 struct{ u user2 }
)

func (r *userResolver2) ID() graphql.ID                               { return graphql.ID(r.u.id) }
func (r *userResolver2) Name() *string                                { return &r.u.name }
func (r *userResolver2) Email() *string                               { return &r.u.email }
func (r *userResolver2) Friends(ctx context.Context) []*userResolver2 { return nil }

type root2 struct{}

func (r *root2) User(ctx context.Context, args struct{ ID string }) *userResolver2 {
	if graphql.HasSelectedField(ctx, "email") {
		fmt.Println("email requested")
	}
	if graphql.HasSelectedField(ctx, "friends") {
		fmt.Println("friends requested")
	}
	return &userResolver2{u: user2{id: args.ID, name: "Alice", email: "a@example.com"}}
}

// Example_hasSelectedField demonstrates HasSelectedField helper for conditional
// logic without needing the full slice of field names. This can be handy when
// checking for a small number of specific fields (avoids allocating the names
// slice if it hasn't already been built).
func main() {
	const s = `
		schema { query: Query }
		type Query { user(id: ID!): User }
		type User { id: ID! name: String email: String friends: [User!]! }
	`
	schema := graphql.MustParseSchema(s, &root2{})
	// Select a subset of fields including a nested composite field; friends requires its own selection set.
	query := `query { user(id: "U1") { id email friends { id } } }`
	_ = schema.Exec(context.Background(), query, "", nil)
}
Output:

email requested
friends requested
Example (InputArray)

Example_inputArray shows a simple GraphQL schema which defines a custom input type. Then it executes a query against it passing array arguments.

package main

import (
	"context"
	"encoding/json"
	"os"

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

type query struct{}

type IntTuple struct {
	A int32
	B int32
}

func (*query) Reversed(args struct{ Values []string }) []string {
	result := make([]string, len(args.Values))

	for i, value := range args.Values {
		for _, v := range value {
			result[i] = string(v) + result[i]
		}
	}
	return result
}

func (*query) Sums(args struct{ Values []IntTuple }) []int32 {
	result := make([]int32, len(args.Values))

	for i, value := range args.Values {
		result[i] = value.A + value.B
	}
	return result
}

// Example_inputArray shows a simple GraphQL schema which defines a custom input type.
// Then it executes a query against it passing array arguments.
func main() {
	s := `
	  input IntTuple {
	    a: Int!
	    b: Int!
	  }
	
	  type Query {
	    reversed(values: [String!]!): [String!]!
	    sums(values: [IntTuple!]!): [Int!]!
	  }
	`
	schema := graphql.MustParseSchema(s, &query{})

	query := `
	  query{
	    reversed(values:["hello", "hi"])
	    sums(values:[{a:2,b:3},{a:-10,b:-1}])
	  }
	`

	res := schema.Exec(context.Background(), query, "", nil)

	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	err := enc.Encode(res)
	if err != nil {
		panic(err)
	}

}
Output:

{
  "data": {
    "reversed": [
      "olleh",
      "ih"
    ],
    "sums": [
      5,
      -11
    ]
  }
}
Example (PrefetchData)

ExamplePrefetchData demonstrates data prefetching for a 3-level hierarchy depending on the requested fields.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"sort"

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

// In this example we demonstrate a 3-level hierarchy (Category -> Products -> Reviews)
// and show how to prefetch nested data (products & reviews) in a single pass using
// the selected field/argument inspection helpers.
// The type names are prefixed with "pf" to avoid clashing with other examples in this package.
type pfCategory struct {
	ID   string
	Name string
}
type pfProduct struct {
	ID         string
	CategoryID string
	Name       string
	Price      int
}
type pfReview struct {
	ID        string
	ProductID string
	Body      string
	Stars     int32
}

var (
	pfCategories = []pfCategory{{"C1", "Electronics"}}
	pfProducts   = []pfProduct{
		{"P01", "C1", "Adapter", 15},
		{"P02", "C1", "Battery", 25},
		{"P03", "C1", "Cable", 5},
		{"P04", "C1", "Dock", 45},
		{"P05", "C1", "Earbuds", 55},
		{"P06", "C1", "Fan", 35},
		{"P07", "C1", "Gamepad", 65},
		{"P08", "C1", "Hub", 40},
		{"P09", "C1", "Indicator", 12},
		{"P10", "C1", "Joystick", 70},
		{"P11", "C1", "Keyboard", 80},
		{"P12", "C1", "Light", 8},
		{"P13", "C1", "Microphone", 120},
	}
	pfReviews = []pfReview{
		{"R01", "P05", "Great sound", 5},
		{"R02", "P05", "Decent", 4},
		{"R03", "P05", "Could be louder", 3},
		{"R04", "P05", "Nice fit", 5},
		{"R05", "P05", "Battery ok", 4},
		{"R06", "P05", "Color faded", 2},
		{"R07", "P05", "Value for money", 5},
		{"R08", "P11", "Fast typing", 5},
		{"R09", "P11", "Loud keys", 3},
		{"R10", "P02", "Holds charge", 4},
		{"R11", "P02", "Gets warm", 2},
	}
)

// SDL describing the hierarchy with pagination & ordering arguments.
const prefetchSDL = `
schema { query: Query }

enum ProductOrder {
	NAME
	PRICE
}

type Query {
	category(id: ID!): Category
}

type Category {
	id: ID!
	name: String!
	products(after: ID, first: Int, orderBy: ProductOrder): [Product!]!
}

type Product {
	id: ID!
	name: String!
	price: Int!
	reviews(last: Int = 5): [Review!]!
}

type Review {
	id: ID!
	body: String!
	stars: Int!
}
`

type pfRoot struct{}

// ProductOrder represented as plain string for simplicity in this example.
type ProductOrder string

const (
	ProductOrderName  ProductOrder = "NAME"
	ProductOrderPrice ProductOrder = "PRICE"
)

func (r *pfRoot) Category(ctx context.Context, args struct{ ID graphql.ID }) *pfCategoryResolver {
	var cat *pfCategory
	for i := range pfCategories {
		if pfCategories[i].ID == string(args.ID) {
			cat = &pfCategories[i]
			break
		}
	}
	if cat == nil {
		return nil
	}

	cr := &pfCategoryResolver{c: cat}

	// Exit early if "products" field wasn't requested
	if !graphql.HasSelectedField(ctx, "products") {
		return cr
	}

	// Prefetch products for this category
	// Decode any arguments provided to the "products" field
	// and apply them during prefetching.
	var prodArgs struct {
		After   graphql.ID
		First   *int32
		OrderBy *string
	}
	_, _ = graphql.DecodeSelectedFieldArgs(ctx, "products", &prodArgs)
	firstVal := int32(10)
	if prodArgs.First != nil && *prodArgs.First > 0 {
		firstVal = *prodArgs.First
	}
	orderVal := ProductOrderName
	if prodArgs.OrderBy != nil && *prodArgs.OrderBy != "" {
		orderVal = ProductOrder(*prodArgs.OrderBy)
	}
	filtered := make([]pfProduct, 0, 16)
	for _, p := range pfProducts {
		if p.CategoryID == cat.ID {
			filtered = append(filtered, p)
		}
	}
	switch orderVal {
	case ProductOrderPrice:
		sort.Slice(filtered, func(i, j int) bool { return filtered[i].Price < filtered[j].Price })
	default:
		sort.Slice(filtered, func(i, j int) bool { return filtered[i].Name < filtered[j].Name })
	}
	var start int
	if prodArgs.After != "" {
		for i, p := range filtered {
			if p.ID == string(prodArgs.After) {
				start = i + 1
				break
			}
		}
		if start > len(filtered) {
			start = len(filtered)
		}
	}
	end := start + int(firstVal)
	if end > len(filtered) {
		end = len(filtered)
	}
	slice := filtered[start:end]
	cr.prefetchedProducts = make([]*pfProduct, len(slice))
	for i := range slice {
		prod := slice[i]
		cr.prefetchedProducts[i] = &prod
	}

	// Exit early if "reviews" sub-field wasn't requested for the products
	if !graphql.HasSelectedField(ctx, "products.reviews") {
		return cr
	}

	// Prefetch reviews for all products in this category
	// Decode any arguments provided to the "reviews" field
	// and apply them during prefetching.
	var reviewArgs struct{ Last int32 }
	_, _ = graphql.DecodeSelectedFieldArgs(ctx, "products.reviews", &reviewArgs)
	var lastVal int32
	if reviewArgs.Last > 0 {
		lastVal = reviewArgs.Last
	}
	take := int(lastVal)
	cr.reviewsByProduct = make(map[string][]*pfReview)
	productSet := map[string]struct{}{}
	for _, p := range cr.prefetchedProducts {
		productSet[p.ID] = struct{}{}
	}
	for i := range pfReviews {
		rv := pfReviews[i]
		if _, ok := productSet[rv.ProductID]; !ok {
			continue
		}
		arr := cr.reviewsByProduct[rv.ProductID]
		arr = append(arr, &rv)
		if take > 0 && len(arr) > take {
			arr = arr[len(arr)-take:]
		}
		cr.reviewsByProduct[rv.ProductID] = arr
	}
	return cr
}

type pfCategoryResolver struct {
	c                  *pfCategory
	prefetchedProducts []*pfProduct
	reviewsByProduct   map[string][]*pfReview
}

func (c *pfCategoryResolver) ID() graphql.ID { return graphql.ID(c.c.ID) }
func (c *pfCategoryResolver) Name() string   { return c.c.Name }

type pfProductArgs struct {
	After   *graphql.ID
	First   *int32
	OrderBy *string
}

func (c *pfCategoryResolver) Products(ctx context.Context, args pfProductArgs) ([]*pfProductResolver, error) {
	out := make([]*pfProductResolver, len(c.prefetchedProducts))
	for i, p := range c.prefetchedProducts {
		out[i] = &pfProductResolver{parent: c, p: p}
	}
	return out, nil
}

type pfProductResolver struct {
	parent *pfCategoryResolver
	p      *pfProduct
}

func (p *pfProductResolver) ID() graphql.ID { return graphql.ID(p.p.ID) }
func (p *pfProductResolver) Name() string   { return p.p.Name }
func (p *pfProductResolver) Price() int32   { return int32(p.p.Price) }
func (p *pfProductResolver) Reviews(ctx context.Context, args struct{ Last int32 }) ([]*pfReviewResolver, error) {
	rs := p.parent.reviewsByProduct[p.p.ID]
	out := make([]*pfReviewResolver, len(rs))
	for i, r := range rs {
		out[i] = &pfReviewResolver{r: r}
	}
	return out, nil
}

type pfReviewResolver struct{ r *pfReview }

func (r *pfReviewResolver) ID() graphql.ID { return graphql.ID(r.r.ID) }
func (r *pfReviewResolver) Body() string   { return r.r.Body }
func (r *pfReviewResolver) Stars() int32   { return r.r.Stars }

// ExamplePrefetchData demonstrates data prefetching for a 3-level hierarchy depending on the requested fields.
func main() {
	schema := graphql.MustParseSchema(prefetchSDL, &pfRoot{})

	// Query 1: order products by NAME, starting after P02, first 5, with default last 5 reviews.
	q1 := `{
  category(id:"C1") {
    id
    name
    products(after:"P02", first:5, orderBy: NAME) {
      id
      name
      price
      reviews {
        id
        stars
      }
    }
  }
}`

	// Query 2: order by PRICE, no cursor (after), first 4 products only.
	q2 := `{
  category(id:"C1") {
    products(first:4, orderBy: PRICE) {
      id
      name
      price
    }
  }
}`

	fmt.Println("Order by NAME result:")
	res1 := schema.Exec(context.Background(), q1, "", nil)
	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	_ = enc.Encode(res1)

	fmt.Println("Order by PRICE result:")
	res2 := schema.Exec(context.Background(), q2, "", nil)
	enc = json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	_ = enc.Encode(res2)

}
Output:

Order by NAME result:
{
  "data": {
    "category": {
      "id": "C1",
      "name": "Electronics",
      "products": [
        {
          "id": "P03",
          "name": "Cable",
          "price": 5,
          "reviews": []
        },
        {
          "id": "P04",
          "name": "Dock",
          "price": 45,
          "reviews": []
        },
        {
          "id": "P05",
          "name": "Earbuds",
          "price": 55,
          "reviews": [
            {
              "id": "R01",
              "stars": 5
            },
            {
              "id": "R02",
              "stars": 4
            },
            {
              "id": "R03",
              "stars": 3
            },
            {
              "id": "R04",
              "stars": 5
            },
            {
              "id": "R05",
              "stars": 4
            },
            {
              "id": "R06",
              "stars": 2
            },
            {
              "id": "R07",
              "stars": 5
            }
          ]
        },
        {
          "id": "P06",
          "name": "Fan",
          "price": 35,
          "reviews": []
        },
        {
          "id": "P07",
          "name": "Gamepad",
          "price": 65,
          "reviews": []
        }
      ]
    }
  }
}
Order by PRICE result:
{
  "data": {
    "category": {
      "products": [
        {
          "id": "P03",
          "name": "Cable",
          "price": 5
        },
        {
          "id": "P12",
          "name": "Light",
          "price": 8
        },
        {
          "id": "P09",
          "name": "Indicator",
          "price": 12
        },
        {
          "id": "P01",
          "name": "Adapter",
          "price": 15
        }
      ]
    }
  }
}
Example (ResolverFieldTag)

ExampleFieldTag demonstrates the use of the graphql field tag.

type resolver struct {
	Hello           string
	HelloUnderscore string `graphql:"_hello"`
	HelloLower      string `graphql:"hello"`
	HelloTitle      string `graphql:"Hello"`
	HelloUpper      string `graphql:"HELLO"`
}

sdl := `
	type Query {
		_hello: String!
		hello: String!
		Hello: String!
		HELLO: String!
	}`

r := &resolver{
	Hello:           "This field is not used during query execution!",
	HelloLower:      "Hello, graphql!",
	HelloTitle:      "Hello, GraphQL!",
	HelloUnderscore: "Hello, _!",
	HelloUpper:      "Hello, GRAPHQL!",
}

query := `
	{
		_hello
		hello
		Hello
		HELLO
	}
	`

schema := graphql.MustParseSchema(sdl, r, graphql.UseFieldResolvers())
res := schema.Exec(context.Background(), query, "", nil)

enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "  ")
err := enc.Encode(res)
if err != nil {
	panic(err)
}
Output:

{
  "data": {
    "_hello": "Hello, _!",
    "hello": "Hello, graphql!",
    "Hello": "Hello, GraphQL!",
    "HELLO": "Hello, GRAPHQL!"
  }
}
Example (SelectedFieldNames)

Example_selectedFieldNames demonstrates SelectedFieldNames usage in a resolver for conditional data fetching (e.g. building a DB projection list).

package main

import (
	"context"
	"fmt"

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

type (
	user         struct{ id, name, email string }
	userResolver struct{ u user }
)

func (r *userResolver) ID() graphql.ID { return graphql.ID(r.u.id) }
func (r *userResolver) Name() *string  { return &r.u.name }
func (r *userResolver) Email() *string { return &r.u.email }
func (r *userResolver) Friends(ctx context.Context) []*userResolver {
	// Return a couple of dummy friends (data itself not important for field selection example)
	return []*userResolver{
		{u: user{id: "F1", name: "Bob"}},
		{u: user{id: "F2", name: "Carol"}},
	}
}

type root struct{}

func (r *root) User(ctx context.Context, args struct{ ID string }) *userResolver {
	fields := graphql.SelectedFieldNames(ctx)
	fmt.Println(fields)
	return &userResolver{u: user{id: args.ID, name: "Alice", email: "a@example.com"}}
}

// Example_selectedFieldNames demonstrates SelectedFieldNames usage in a resolver for
// conditional data fetching (e.g. building a DB projection list).
func main() {
	const s = `
        schema { query: Query }
        type Query { user(id: ID!): User }
        type User { id: ID! name: String email: String friends: [User!]! }
    `
	schema := graphql.MustParseSchema(s, &root{})
	query := `query { user(id: "U1") { id name friends { id name } } }`
	_ = schema.Exec(context.Background(), query, "", nil)
}
Output:

[id name friends friends.id friends.name]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeSelectedFieldArgs added in v1.8.0

func DecodeSelectedFieldArgs(ctx context.Context, path string, dst interface{}) (bool, error)

DecodeSelectedFieldArgs decodes the argument map for the given path into dst. It returns ok=false if the path or its arguments are absent. Results are cached per (path, concrete struct type) to avoid repeated reflection cost; repeated successful decodes copy a previously cached value into dst.

Example:

type BooksArgs struct { Top int32 }
var args BooksArgs
ok, err := graphql.DecodeSelectedFieldArgs(ctx, "books", &args)
if ok { /* use args.Top */ }

func HasSelectedField added in v1.7.0

func HasSelectedField(ctx context.Context, name string) bool

HasSelectedField returns true if the child selection list contains the provided (possibly nested) path (case sensitive). It returns false for leaf resolvers and when DisableFieldSelections was used.

func SelectedFieldNames added in v1.7.0

func SelectedFieldNames(ctx context.Context) []string

SelectedFieldNames returns the set of selected field paths underneath the current resolver. Paths are dot-delimited for nested structures (e.g. "products", "products.id", "products.category.id"). Immediate child field names are always present (even when they have further children). Order preserves the first appearance in the query after fragment flattening, performing a depth-first traversal. It returns an empty slice when the current field's return type is a leaf (scalar / enum) or when DisableFieldSelections was used at schema creation. The returned slice is a copy safe for caller modification.

Notes:

  • Fragment spreads & inline fragments are flattened.
  • Field aliases are ignored; original schema field names are used.
  • Meta fields beginning with "__" (including __typename) are excluded.
  • Duplicate paths are removed, preserving the earliest occurrence.

func SortedSelectedFieldNames added in v1.7.0

func SortedSelectedFieldNames(ctx context.Context) []string

SortedSelectedFieldNames returns the same data as SelectedFieldNames but sorted lexicographically for deterministic ordering scenarios (e.g. cache key generation). It will also return an empty slice when selections are disabled.

Types

type ID

type ID string

ID represents GraphQL's "ID" scalar type. A custom type may be used instead.

Example
schemaString := `
		schema {
			query: Query
		}

		type Query {
			post: Post!
		}

		type Post {
			id: ID!
			title: String!
		}
	`

resolver := &Resolver{
	post: &Post{
		id:    graphql.ID("5"),
		title: "title",
	},
}

schema := graphql.MustParseSchema(schemaString, resolver)

query := `
		query {
			post {
				id
				title
			}
		}
	`

res := schema.Exec(context.Background(), query, "", nil)

enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "  ")
err := enc.Encode(res)
if err != nil {
	panic(err)
}
Output:

{
  "data": {
    "post": {
      "id": "5",
      "title": "title"
    }
  }
}

func (ID) ImplementsGraphQLType

func (ID) ImplementsGraphQLType(name string) bool

func (ID) MarshalJSON

func (id ID) MarshalJSON() ([]byte, error)

func (*ID) UnmarshalGraphQL

func (id *ID) UnmarshalGraphQL(input interface{}) error

type NullBool

type NullBool struct {
	Value *bool
	Set   bool
}

NullBool is a boolean that can be null. Use it in input structs to differentiate a value explicitly set to null from an omitted value. When the value is defined (either null or a value) Set is true.

Example

ExampleNullBool demonstrates how to use nullable Bool type when it is necessary to differentiate between nil and not set.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"

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

type mutnb struct{}

func (*mutnb) Toggle(args struct{ Enabled graphql.NullBool }) string {
	if !args.Enabled.Set {
		return "input value was not provided"
	} else if args.Enabled.Value == nil {
		return "enabled is 'null'"
	}
	return fmt.Sprintf("enabled '%v'", *args.Enabled.Value)
}

func (r *mutnb) Name() string {
	return "test"
}

// ExampleNullBool demonstrates how to use nullable Bool type when it is necessary to differentiate between nil and not set.
func main() {
	const s = `
		schema {
			query: Query
			mutation: Mutation
		}
		type Query{
			name: String!
		}
		type Mutation{
			toggle(enabled: Boolean): String!
		}
	`
	schema := graphql.MustParseSchema(s, &mutnb{})

	const query = `mutation{
		toggle1: toggle()
		toggle2: toggle(enabled: null)
		toggle3: toggle(enabled: true)
	}`
	res := schema.Exec(context.Background(), query, "", nil)

	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	err := enc.Encode(res)
	if err != nil {
		panic(err)
	}

}
Output:

{
  "data": {
    "toggle1": "input value was not provided",
    "toggle2": "enabled is 'null'",
    "toggle3": "enabled 'true'"
  }
}

func (NullBool) ImplementsGraphQLType

func (NullBool) ImplementsGraphQLType(name string) bool

func (*NullBool) Nullable

func (s *NullBool) Nullable()

func (*NullBool) UnmarshalGraphQL

func (s *NullBool) UnmarshalGraphQL(input interface{}) error

type NullFloat

type NullFloat struct {
	Value *float64
	Set   bool
}

NullFloat is a float that can be null. Use it in input structs to differentiate a value explicitly set to null from an omitted value. When the value is defined (either null or a value) Set is true.

func (NullFloat) ImplementsGraphQLType

func (NullFloat) ImplementsGraphQLType(name string) bool

func (*NullFloat) Nullable

func (s *NullFloat) Nullable()

func (*NullFloat) UnmarshalGraphQL

func (s *NullFloat) UnmarshalGraphQL(input interface{}) error

type NullID added in v1.6.0

type NullID struct {
	Value *ID
	Set   bool
}

NullID is an ID that can be null. Use it in input structs to differentiate a value explicitly set to null from an omitted value. When the value is defined (either null or a value) Set is true.

func (NullID) ImplementsGraphQLType added in v1.6.0

func (NullID) ImplementsGraphQLType(name string) bool

func (*NullID) Nullable added in v1.6.0

func (s *NullID) Nullable()

func (*NullID) UnmarshalGraphQL added in v1.6.0

func (s *NullID) UnmarshalGraphQL(input interface{}) error

type NullInt

type NullInt struct {
	Value *int32
	Set   bool
}

NullInt is an int that can be null. Use it in input structs to differentiate a value explicitly set to null from an omitted value. When the value is defined (either null or a value) Set is true.

func (NullInt) ImplementsGraphQLType

func (NullInt) ImplementsGraphQLType(name string) bool

func (*NullInt) Nullable

func (s *NullInt) Nullable()

func (*NullInt) UnmarshalGraphQL

func (s *NullInt) UnmarshalGraphQL(input interface{}) error

type NullString

type NullString struct {
	Value *string
	Set   bool
}

NullString is a string that can be null. Use it in input structs to differentiate a value explicitly set to null from an omitted value. When the value is defined (either null or a value) Set is true.

func (NullString) ImplementsGraphQLType

func (NullString) ImplementsGraphQLType(name string) bool

func (*NullString) Nullable

func (s *NullString) Nullable()

func (*NullString) UnmarshalGraphQL

func (s *NullString) UnmarshalGraphQL(input interface{}) error

type NullTime

type NullTime struct {
	Value *Time
	Set   bool
}

NullTime is a time value that can be null. Use it in input structs to differentiate a value explicitly set to null from an omitted value. When the value is defined (either null or a value) Set is true.

func (NullTime) ImplementsGraphQLType

func (NullTime) ImplementsGraphQLType(name string) bool

func (*NullTime) Nullable

func (s *NullTime) Nullable()

func (*NullTime) UnmarshalGraphQL

func (s *NullTime) UnmarshalGraphQL(input interface{}) error

type Response

type Response struct {
	Errors     []*errors.QueryError   `json:"errors,omitempty"`
	Data       json.RawMessage        `json:"data,omitempty"`
	Extensions map[string]interface{} `json:"extensions,omitempty"`
}

Response represents a typical response of a GraphQL server. It may be encoded to JSON directly or it may be further processed to a custom response type, for example to include custom error data. Errors are intentionally serialized first based on the advice in the spec.

type Schema

type Schema struct {
	// contains filtered or unexported fields
}

Schema represents a GraphQL schema with an optional resolver.

func MustParseSchema

func MustParseSchema(schemaString string, resolver interface{}, opts ...SchemaOpt) *Schema

MustParseSchema calls ParseSchema and panics on error.

func ParseSchema

func ParseSchema(schemaString string, resolver interface{}, opts ...SchemaOpt) (*Schema, error)

ParseSchema parses a GraphQL schema and attaches the given root resolver. It returns an error if the Go type signature of the resolvers does not match the schema. If nil is passed as the resolver, then the schema can not be executed, but it may be inspected (e.g. with Schema.ToJSON or Schema.AST).

func (*Schema) AST added in v1.6.0

func (s *Schema) AST() *ast.Schema

AST returns the abstract syntax tree of the GraphQL schema definition. It in turn can be used by other tools such as validators or generators.

Example
schema := graphql.MustParseSchema(starwars.Schema, nil)
ast := schema.AST()

for _, e := range ast.Enums {
	fmt.Printf("Enum %q has the following options:\n", e.Name)
	for _, o := range e.EnumValuesDefinition {
		fmt.Printf("  - %s\n", o.EnumValue)
	}
}
Output:

Enum "Episode" has the following options:
  - NEWHOPE
  - EMPIRE
  - JEDI
Enum "LengthUnit" has the following options:
  - METER
  - FOOT
Example (GenerateEnum)
s := `
		type Query {
			currentSeason: Season!
		}

		"""
		Season represents a season of the year.
		"""
		enum Season {
			SPRING
			SUMMER
			AUTUMN
			WINTER
		}
	`

gocode := `
{{ $enum := . }}
// {{ $enum.Desc }}
type {{ $enum.Name }} int

const (
	{{ range $i, $e :=  $enum.EnumValuesDefinition }}{{ if ne $i 0 }}{{ printf "\n\t" }}{{ end }}
		{{- $e.EnumValue | toVar }}{{ if eq $i 0 }} {{ $enum.Name }} = iota{{ end }}
	{{- end }}
)

var {{ $enum.Name | toLower }}Items = [...]string{
{{- range $i, $e :=  $enum.EnumValuesDefinition }}{{ if ne $i 0 }}{{ printf ", " }}{{ end }}
	{{- $e.EnumValue | quote }}
{{- end -}}
}

func (s {{ $enum.Name }}) String() string { return {{ $enum.Name | toLower }}Items[s] }

func (s *{{ $enum.Name }}) Deserialize(str string) {
	var found bool
	for i, v := range {{ $enum.Name | toLower }}Items {
		if v == str {
			found = true
			(*s) = {{ $enum.Name }}(i)
		}
	}
	if !found {
		panic("invalid value for enum {{ $enum.Name }}: " + str)
	}
}

func ({{ $enum.Name }}) ImplementsGraphQLType(name string) bool {
	return name == {{ $enum.Name | quote }}
}

func (s *{{ $enum.Name }}) UnmarshalGraphQL(input interface{}) error {
	var err error
	switch input := input.(type) {
	case string:
		s.Deserialize(input)
	default:
		err = fmt.Errorf("wrong type for {{ $enum.Name }}: %T", input)
	}
	return err
}
`

funcs := template.FuncMap{
	"quote": func(s string) string {
		return `"` + s + `"`
	},
	"toLower": strings.ToLower,
	"toVar": func(s string) string {
		if len(s) == 0 {
			return s
		}
		return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
	},
}

tpl, err := template.New("enum").Funcs(funcs).Parse(gocode)
if err != nil {
	panic(err)
}

opts := []graphql.SchemaOpt{
	graphql.UseStringDescriptions(),
}
schema := graphql.MustParseSchema(s, nil, opts...)
ast := schema.AST()
seasons := ast.Enums[0]

err = tpl.Execute(os.Stdout, seasons)
if err != nil {
	panic(err)
}
Output:

// Season represents a season of the year.
type Season int

const (
	Spring Season = iota
	Summer
	Autumn
	Winter
)

var seasonItems = [...]string{"SPRING", "SUMMER", "AUTUMN", "WINTER"}

func (s Season) String() string { return seasonItems[s] }

func (s *Season) Deserialize(str string) {
	var found bool
	for i, v := range seasonItems {
		if v == str {
			found = true
			(*s) = Season(i)
		}
	}
	if !found {
		panic("invalid value for enum Season: " + str)
	}
}

func (Season) ImplementsGraphQLType(name string) bool {
	return name == "Season"
}

func (s *Season) UnmarshalGraphQL(input interface{}) error {
	var err error
	switch input := input.(type) {
	case string:
		s.Deserialize(input)
	default:
		err = fmt.Errorf("wrong type for Season: %T", input)
	}
	return err
}

func (*Schema) ASTSchema deprecated added in v1.1.0

func (s *Schema) ASTSchema() *ast.Schema

ASTSchema returns the abstract syntax tree of the GraphQL schema definition.

Deprecated: use Schema.AST instead.

func (*Schema) Exec

func (s *Schema) Exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) *Response

Exec executes the given query with the schema's resolver. It panics if the schema was created without a resolver. If the context get cancelled, no further resolvers will be called and a the context error will be returned as soon as possible (not immediately).

func (*Schema) Inspect

func (s *Schema) Inspect() *introspection.Schema

Inspect allows inspection of the given schema.

func (*Schema) Subscribe

func (s *Schema) Subscribe(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) (<-chan interface{}, error)

Subscribe returns a response channel for the given subscription with the schema's resolver. It returns an error if the schema was created without a resolver. If the context gets cancelled, the response channel will be closed and no further resolvers will be called. The context error will be returned as soon as possible (not immediately).

func (*Schema) ToJSON

func (s *Schema) ToJSON() ([]byte, error)

ToJSON encodes the schema in a JSON format used by tools like Relay.

func (*Schema) Validate

func (s *Schema) Validate(queryString string) []*errors.QueryError

Validate validates the given query with the schema.

func (*Schema) ValidateWithVariables

func (s *Schema) ValidateWithVariables(queryString string, variables map[string]interface{}) []*errors.QueryError

ValidateWithVariables validates the given query with the schema and the input variables.

type SchemaOpt

type SchemaOpt func(*Schema)

SchemaOpt is an option to pass to ParseSchema or MustParseSchema.

func DisableFieldSelections added in v1.7.0

func DisableFieldSelections() SchemaOpt

DisableFieldSelections disables capturing child field selections for the SelectedFieldNames / HasSelectedField helpers. When disabled, those helpers will always return an empty result / false (i.e. zero-value) and no per-resolver selection context is stored. This is an opt-out for applications that never intend to use the feature and want to avoid even its small lazy overhead.

func DisableIntrospection deprecated

func DisableIntrospection() SchemaOpt

DisableIntrospection disables introspection queries. This function is left for backwards compatibility reasons and is just a shorthand for:

filter := func(context.Context) bool {
   return false
}
graphql.RestrictIntrospection(filter)

Deprecated: use RestrictIntrospection filter instead. Do not use it together with RestrictIntrospection, otherwise the option added last takes precedence.

func Logger

func Logger(logger log.Logger) SchemaOpt

Logger is used to log panics during query execution. It defaults to log.DefaultLogger.

func MaxDepth

func MaxDepth(n int) SchemaOpt

MaxDepth specifies the maximum field nesting depth in a query. The default is 0 which disables max depth checking.

Example
schema := graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{}, graphql.MaxDepth(3))

// this query has a depth of 4
query := `
	  query {
	    hero(episode:EMPIRE) { # level 1
	      name                 # level 2
	      friends { 
	        name               # level 3
	        friends { 
	          id               # level 4 - this would exceed the max depth
	        }
	      }
	    }
	  }`

res := schema.Exec(context.Background(), query, "", nil)

enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "  ")
err := enc.Encode(res)
if err != nil {
	panic(err)
}
Output:

{
  "errors": [
    {
      "message": "Field \"id\" has depth 4 that exceeds max depth 3",
      "locations": [
        {
          "line": 8,
          "column": 12
        }
      ]
    }
  ]
}

func MaxParallelism

func MaxParallelism(n int) SchemaOpt

MaxParallelism specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10.

func MaxQueryLength added in v1.6.0

func MaxQueryLength(n int) SchemaOpt

MaxQueryLength specifies the maximum allowed query length in bytes. The default is 0 which disables max length checking.

Example
schema := graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{}, graphql.MaxQueryLength(50))

// this query has a length of 53
query := `{
	  hero(episode:EMPIRE) {
	    id
	    name
	  }
	}`

res := schema.Exec(context.Background(), query, "", nil)

enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "  ")
err := enc.Encode(res)
if err != nil {
	panic(err)
}
Output:

{
  "errors": [
    {
      "message": "query length 53 exceeds the maximum allowed query length of 50 bytes"
    }
  ]
}

func OverlapValidationLimit added in v1.7.1

func OverlapValidationLimit(n int) SchemaOpt

OverlapValidationLimit caps the number of overlapping selection pairs that will be examined during validation of a single operation (including fragments). A value of 0 disables the cap. When the cap is exceeded validation aborts early with an error (rule: OverlapValidationLimitExceeded) to protect against maliciously constructed queries designed to exhaust memory/CPU.

func PanicHandler added in v1.3.0

func PanicHandler(panicHandler errors.PanicHandler) SchemaOpt

PanicHandler is used to customize the panic errors during query execution. It defaults to errors.DefaultPanicHandler.

func RestrictIntrospection added in v1.6.0

func RestrictIntrospection(fn func(ctx context.Context) bool) SchemaOpt

RestrictIntrospection accepts a filter func. If this function returns false the introspection is disabled, otherwise it is enabled. If this option is not provided the introspection is enabled by default. This option is useful for allowing introspection only to admin users, for example:

filter := func(ctx context.Context) bool {
	u, ok := user.FromContext(ctx)
	return ok && u.IsAdmin()
}

Do not use it together with DisableIntrospection, otherwise the option added last takes precedence.

Example
type allowKeyType struct{}
allowKey := allowKeyType{}
// only allow introspection if the function below returns true
filter := func(ctx context.Context) bool {
	allow, found := ctx.Value(allowKey).(bool)
	return found && allow
}
schema := graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{}, graphql.RestrictIntrospection(filter))

query := `{
		__type(name: "Episode") {
			enumValues {
				name
			}
		}
	}`

cases := []struct {
	name string
	ctx  context.Context
}{
	{
		name: "Empty context",
		ctx:  context.Background(),
	},
	{
		name: "Introspection forbidden",
		ctx:  context.WithValue(context.Background(), allowKey, false),
	},
	{
		name: "Introspection allowed",
		ctx:  context.WithValue(context.Background(), allowKey, true),
	},
}
for _, c := range cases {
	fmt.Println(c.name, "result:")
	res := schema.Exec(c.ctx, query, "", nil)

	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	err := enc.Encode(res)
	if err != nil {
		panic(err)
	}
}
Output:

Empty context result:
{
  "data": {}
}
Introspection forbidden result:
{
  "data": {}
}
Introspection allowed result:
{
  "data": {
    "__type": {
      "enumValues": [
        {
          "name": "NEWHOPE"
        },
        {
          "name": "EMPIRE"
        },
        {
          "name": "JEDI"
        }
      ]
    }
  }
}

func SubscribeResolverTimeout

func SubscribeResolverTimeout(timeout time.Duration) SchemaOpt

SubscribeResolverTimeout is an option to control the amount of time we allow for a single subscribe message resolver to complete it's job before it times out and returns an error to the subscriber.

func Tracer

func Tracer(t tracer.Tracer) SchemaOpt

Tracer is used to trace queries and fields. It defaults to noop.Tracer.

func UseFieldResolvers

func UseFieldResolvers() SchemaOpt

UseFieldResolvers specifies whether to use struct fields as resolvers.

func UseStringDescriptions

func UseStringDescriptions() SchemaOpt

UseStringDescriptions enables the usage of double quoted and triple quoted strings as descriptions as per the June 2018 spec. When this is not enabled, comments are parsed as descriptions instead.

Example
s := `
	schema {
		query: Query
	}

	type Query {
		post(id: Int!): Post
	}

	"""
	Post represents a blog post.
	"""
	type Post {
		"Unique identifier of the post."
		id: ID!

		# The title field has no description.
		title: String!

		"""
		Tags of the post.
		"""
		# tags can be empty
		tags: [String!]!
	}
	`

opts := []graphql.SchemaOpt{
	graphql.UseStringDescriptions(),
}
schema := graphql.MustParseSchema(s, nil, opts...)
ast := schema.AST()

post := ast.Objects[1]
fmt.Printf("Field descriptions of the %q type:\n", post.TypeName())
for _, f := range post.Fields {
	fmt.Printf("  field: %q, description: %q\n", f.Name, f.Desc)
}
Output:

Field descriptions of the "Post" type:
  field: "id", description: "Unique identifier of the post."
  field: "title", description: ""
  field: "tags", description: "Tags of the post."

func ValidationTracer

func ValidationTracer(tracer tracer.LegacyValidationTracer) SchemaOpt

ValidationTracer is used to trace validation errors. It defaults to tracer.LegacyNoopValidationTracer. Deprecated: context is needed to support tracing correctly. Use a tracer which implements tracer.ValidationTracer.

type Time

type Time struct {
	time.Time
}

Time is a custom GraphQL type to represent an instant in time. It has to be added to a schema via "scalar Time" since it is not a predeclared GraphQL type like "ID".

Example
package main

import (
	"context"
	"encoding/json"
	"os"
	"time"

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

type tquery struct{}

func (*tquery) CurrentTime() graphql.Time {
	return graphql.Time{Time: time.Date(2023, 2, 6, 12, 3, 22, 0, time.UTC)}
}

func main() {
	const s = `
		scalar Time

		type Query {
			currentTime: Time!
		}
	`
	schema := graphql.MustParseSchema(s, &tquery{})

	const query = "{ currentTime }"
	res := schema.Exec(context.Background(), query, "", nil)

	err := json.NewEncoder(os.Stdout).Encode(res)
	if err != nil {
		panic(err)
	}

}
Output:

{"data":{"currentTime":"2023-02-06T12:03:22Z"}}

func (Time) ImplementsGraphQLType

func (Time) ImplementsGraphQLType(name string) bool

ImplementsGraphQLType maps this custom Go type to the graphql scalar type in the schema.

func (Time) MarshalJSON

func (t Time) MarshalJSON() ([]byte, error)

MarshalJSON is a custom marshaler for Time

This function will be called whenever you query for fields that use the Time type

func (*Time) UnmarshalGraphQL

func (t *Time) UnmarshalGraphQL(input interface{}) error

UnmarshalGraphQL is a custom unmarshaler for Time

This function will be called whenever you use the time scalar as an input

Directories

Path Synopsis
Package ast represents all types from the [GraphQL specification] in code.
Package ast represents all types from the [GraphQL specification] in code.
example
caching/cache
Package cache implements caching of GraphQL requests by allowing resolvers to provide hints about their cacheability, which can be used by the transport handlers (e.g.
Package cache implements caching of GraphQL requests by allowing resolvers to provide hints about their cacheability, which can be used by the transport handlers (e.g.
caching/server command
enum command
Package main demonstrates a simple web app that uses type-safe enums in a GraphQL resolver.
Package main demonstrates a simple web app that uses type-safe enums in a GraphQL resolver.
prefetch command
This example demonstrates a 3-level hierarchy (Author -> Books -> Reviews) with data prefetching at each level to avoid N+1 query problems.
This example demonstrates a 3-level hierarchy (Author -> Books -> Reviews) with data prefetching at each level to avoid N+1 query problems.
social/server command
starwars
Package starwars provides a example schema and resolver based on Star Wars characters.
Package starwars provides a example schema and resolver based on Star Wars characters.
starwars/server command
internal
exec/selections
Package selections is for internal use to share selection context between the execution engine and the public graphql package without creating an import cycle.
Package selections is for internal use to share selection context between the execution engine and the public graphql package without creating an import cycle.
The trace package provides tracing functionality.
The trace package provides tracing functionality.
noop
Package noop defines a no-op tracer implementation.
Package noop defines a no-op tracer implementation.
opentracing
The opentracing package provides tracing functionality using OpenTracing.
The opentracing package provides tracing functionality using OpenTracing.
otel
The otel package provides tracing functionality using OpenTelemetry.
The otel package provides tracing functionality using OpenTelemetry.
tracer
The tracer package provides tracing functionality.
The tracer package provides tracing functionality.
Package types represents all types from the GraphQL specification in code.
Package types represents all types from the GraphQL specification in code.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL