Tools to write high performance GraphQL applications using Go/Golang.

GoDoc CI

graphql-go-tools

Sponsors

WunderGraph

Are you looking for a GraphQL e2e data fetching solution? Supports frameworks like NextJS, type safety with generated clients (e.g. TypeScript), authentication, edge caching, realtime streaming support, federation, schema stitching, etc...

Have a look at: https://wundergraph.com

WunderGraph allows you to treat APIs like packages. Install, share & integrate as simple as npm install.

Tyk

Need a full lifecycle Api Management solution with 1st Class GraphQL Support? Go check out https://tyk.io

Tyk is the best in class FLAPIM solution to manage all your APIs. Turn REST APIs into GraphQL using the GUI in no time thanks to tyk's Universal Data Graph.

Apollo Federation Gateway Replacement

This library can be used as a replacement for the Apollo Federation Gateway. It implements the Apollo Federation Specification.

In addition to the scope of other implementations, this one supports Subscriptions!

Check out the Demo.

Overview

This repository implements low level building blocks to write graphql services in Go.

With this library you could build tools like:

  • proxies
  • caches
  • server/client implementations
  • a WAF
  • be creative! =)

Currently implemented:

  • GraphQL AST as of https://graphql.github.io/graphql-spec/June2018
  • Token lexing
  • AST parsing: parse bytes/string into AST
  • AST printing: print an AST to an io.Writer
    • supports indentation
  • AST validation:
    • all rules from the spec implemented
  • AST visitor:
    • simple visitor: fastest implementation, without field type definition information
    • visitor: a bit more overhead but has field type definitions and other quirks
  • AST normalization
    • remove unnecessary include/skip directives
    • field deduplication
    • field selection merging
    • fragment definition removal
    • fragment spread inlining
    • inline fragment merging
    • remove self aliasing
    • type extension merging
    • remove type extensions
  • Introspection: transforms a graphql schema into a resolvable Data Source
  • AST execution
    • query planning: turns a Query AST into a cacheable execution plan
      • supported DataSources:
        • GraphQL (multiple GraphQL services can be combined)
        • static (static embedded data)
        • REST
    • query execution: takes a context object and executes an execution plan
  • Middleware:
    • Operation Complexity: Calculates the complexity of an operation based on the GitHub algorithm
  • OperationReport: Makes it easy to collect errors during all phases of a request and enables easy error printing according to the GraphQL spec
  • Playground: Easy hosting of GraphQL Playground (no external dependencies, simple middleware)
  • Import Statements: combine multiple GraphQL files into one single schema using #import statements
  • Implements the Apollo Federation Specification: Replacement for Apollo Federation Gateway

Go version Info

This repos uses go modules so make sure to use the latest version of Go.

Docs

https://godoc.org/github.com/jensneuse/graphql-go-tools

Usage

Look into the docs. Other than that, tests definitely help understanding this library.

Testing

make test

Linting

make lint

Performance

Most hot path operations have 0 allocations. You should expect this library to exceed all alternatives in terms of performance. I've compared my implementation vs. others but why trust my numbers? Feel free to add comparisons via PR.

Benchmarks

Parse Kitchen Sink (1020 chars, example from Facebook):

pkg: github.com/jensneuse/graphql-go-tools/pkg/astparser
BenchmarkKitchenSink 	  189426	      5652 ns/op	       0 B/op	       0 allocs/op
BenchmarkKitchenSink 	  198253	      5526 ns/op	       0 B/op	       0 allocs/op
BenchmarkKitchenSink 	  199924	      5553 ns/op	       0 B/op	       0 allocs/op
BenchmarkKitchenSink 	  212695	      5804 ns/op	       0 B/op	       0 allocs/op

CPU and Memory consumption for lexing, parsing as well as most other operations is neglectable, even for larger queries.

Contributors

  • Jens Neuse (Project Lead & Active Maintainer)
  • Mantas Vidutis
    • Contributions to the http proxy & the Context Middleware
  • Jonas Bergner
    • Contributions to the initial version of the parser, contributions to the tests
    • Implemented Type Extension merging #108
  • Patric Vormstein (Active Maintainer)
    • Fixed lexer on windows #92
    • Author of the graphql package to simplify the usage of the library
    • Refactored the http package to simplify usage with http servers
    • Author of the starwars package to enhance testing
  • Sergey Petrunin (Active Maintainer)
    • Helped cleaning up the API of the pipeline package #166
    • Refactored the ast package into multiple files
    • Author of the introspection converter (introspection JSON -> AST)
    • Fixed various bugs in the parser & visitor & printer
    • Refactored and enhanced the astimport package
  • Vasyl
    • Implemented the logic to generate a federation configuration
    • Added federation example

Contributions

Feel free to file an issue in case of bugs. We're open to your ideas to enhance the repository.

You are open to contribute via PR's. Please open an issue to discuss your idea before implementing it so we can have a discussion. Make sure to comply with the linting rules. You must not add untested code.

Comments
  • Federation - Optional variables in subqueries

    Federation - Optional variables in subqueries

    I've stumbled upon something which I'm quite sure works in the Apollo Gateway.

    Simplified schemas which produces the error below.

    User service:

    type User @key(fields: "id") {
      id: ID!
      name: String!
    }
    
    type Query @extends {
      me: User!
    }
    

    Employee service:

    type User @extends @key(fields: "id") {
      id: ID! @external
      employment(companyId: ID!): Employee!
    }
    
    type Employee @key(fields: "id") {
      id: ID!
    }
    

    Time service:

    directive @goField(
      forceResolver: Boolean
      name: String
    ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
    
    scalar LocalTime
    
    type Employee @extends @key(fields: "id") {
      id: ID! @external
      times(date: LocalTime): [TimeEntry]! @goField(forceResolver: true)
    }
    
    type TimeEntry {
      id: ID!
      employee: Employee!
      start: LocalTime!
      end: LocalTime
    }
    

    Query:

    query Times($companyId: ID!, $date: LocalTime) {
      me {
        employment(companyId: $companyId) {
          id
          times(date: $date) {
            id
            employee {
              id
            }
            start
            end
          }
        }
      }
    }
    

    Variables:

    {"companyId":"abc123"}
    

    The result is my federation gateway saying Get "": unsupported protocol scheme "" in engine.Execute and returning a 500-error.

    It has to do with variables and the fact that date is optional. If I provide a value for date the query works fine.

    I'll try to create a failing test in a PR but my tries so far has failed. 🙈

    I have a small repository which reproduces the error at least: https://github.com/argoyle/subgraph-error-repro

  • fix: return after parsing arg fields of type ast.ValueKindList

    fix: return after parsing arg fields of type ast.ValueKindList

    Summary

    Suppose we have this graphql query request with the following query variables:

    Sample request

    query book_sn($id: String) {
      book_attic {
        oreilly_books(
          where: {
            AND: [ # Parsed ast ast.ValueKindList since it contains query variables, instead of the usual ast.ValueKindVariable
              { python: { tag: { eq: "beginner" } } }
              { golang: { serial_number: { eq: $id } } }
              {
                OR: [
                  {
                    AND: [
                      {
                        python: {
                          date_published: { eq: "2022-07-29" }
                        }
                      }
                      {
                        python: { date_purchased: { eq: "2022-07-29" } }
                      }
                    ]
                  }
                  {
                    AND: [
                      {
                        golang: {
                          date_published: { eq: "2022-08-01" }
                        }
                      }
                      {
                        golang: { date_purchased: { eq: "2022-08-01" } }
                      }
                    ]
                  }
                ]
              }
            
          }
        ) {
          jrr_tolkien_books
          george_rr_martin_books
        }
      }
    }
    
    
    {"id": "1801071039"}
    

    This will be stored into an ast.Document which then gets parsed during validation in https://github.com/wundergraph/graphql-go-tools/blob/v1.51.0/pkg/astvalidation/operation_validation.go#L68.

    It then fails within an implementation of EnterArgument() specifically for allVariablesUsedVisitor because it attempts to continue processing a field of type ast.ValueKindList but we only resolve ast.ValueKindVariable fields to its actual ast.VariableValue mapping. Thus, the out of bound index error as we can see from the screenshot below:

    graphql-go-tools-bug

    Changelog

    • Break case condition after iterating all elements of an ast.ValueKindList within the recursive function, veifyValue().
    • Add corresponding test case for under 5.8.4 All Variables Used grouping to capture this case:

    Before:

    --- FAIL: TestExecutionValidation (0.09s)
        --- FAIL: TestExecutionValidation/5.8_Variables (0.01s)
            --- FAIL: TestExecutionValidation/5.8_Variables/5.8.4_All_Variables_Used (0.00s)
                --- FAIL: TestExecutionValidation/5.8_Variables/5.8.4_All_Variables_Used/variables_in_nested_array_object (0.00s)
    panic: runtime error: index out of range [2] with length 2 [recovered]
    	panic: runtime error: index out of range [2] with length 2
    

    After:

    ok  	github.com/wundergraph/graphql-go-tools/pkg/astvalidation	0.940s
    
    $ go test -count=1 ./pkg/astvalidation/...
    ok      github.com/wundergraph/graphql-go-tools/pkg/astvalidation       0.959s
    ?       github.com/wundergraph/graphql-go-tools/pkg/astvalidation/reference     [no test files]
    ok      github.com/wundergraph/graphql-go-tools/pkg/astvalidation/reference/testsgo     0.157s
    

    Tests

    • [x] Manual testing
    • [x] Functional testing

    @jensneuse

  • Can I define custom directive for handler or engine?

    Can I define custom directive for handler or engine?

    Hi, I am using graphql-go-tools as a gateway to proxy query to an internal service and proxy mutation to another service. It goes well so far.

    I found some custom directives in the examples, like @key, @provides, directive @user, directive @hasRole and @GraphQLDataSource, but I can't find the implementation.

    My goal is to use directive to implement authorization validation like directive @hasRole to validate the token and the query input.

    Is it possible to do it with directive or is there a better way to do it?

    Here's my code if it helps.

    	schemaString := "xxxx"
    	upstreamURL := "https://bcgodev/api/graphql"
    	schema, err := graphql.NewSchemaFromString(schemaString)
    	if err != nil {
    		ogrus.Panic(err)
    	}
    	validation, err = mutationSchema.Validate()
    	if err != nil {
    		logrus.Panic(err)
    	}
    	if !validation.Valid {
    		validation.Errors.ErrorByIndex(0)
    		logrus.Panic("mutation schema is not valid:", validation.Errors.Error(), "first one is:", validation.Errors.ErrorByIndex(0))
    	}
    defaultClient := httpclient.DefaultNetHttpClient
    
    	factory := graphfederation.NewEngineConfigV2Factory(
    		&http.Client{
    			Transport: &http.Transport{
    				MaxIdleConns:    10,
    				IdleConnTimeout: 30 * time.Second,
    			},
    			CheckRedirect: defaultClient.CheckRedirect,
    			Jar:           defaultClient.Jar,
    			Timeout:       defaultClient.Timeout,
    		},
    		graphql_datasource.Configuration{
    			Fetch: graphql_datasource.FetchConfiguration{
    				URL: upstreamURL,
    			},
    			Federation: graphql_datasource.FederationConfiguration{
    				Enabled:    true,
    				ServiceSDL: string(schema.Document()),
    			},
    		},
    	)conf, err := factory.EngineV2Configuration()
    	if err != nil {
    		logrus.Panic(err)
    	}
    
    	executionEngine, _ := graphql.NewExecutionEngineV2(context.TODO(), abstractlogger.NewLogrusLogger(logrus.New(), log.DebugLevel), conf)
    	mergedSchema, err := factory.MergedSchema()
    	if err != nil {
    		logrus.Panic(err)
    	}
    	ghHandler := http2.NewGraphqlHTTPHandler(mergedSchema, executionEngine, nil, logger)
    
    	gin.SetMode(gin.DebugMode)
    	engine := gin.Default()
    
    	engine.POST("/graphql", gin.WrapH(ghHandler))
    
  • fix broken object variable rendering when handling strings

    fix broken object variable rendering when handling strings

    This PR solves a problem with rendering object variables when the content is a string. A test has been added in the graphql package.

    Problem When using object variable rendering strings are always being rendered with quotes, e.g. {{ .object.name }} => "lucky"

    This behavior results in a problem when being used in a URL: https://petlibrary.fake/{{ .object.name }} => https://petlibrary.fake/"lucky"

  • Skipped fields are set to null

    Skipped fields are set to null

    When a selection includes a field with a @skip or @include directive, graphql-go-tools correctly forwards this directive to the upstream service (this is also what Apollo does). If the skip condition is true, the upstream service omits the field from the response. graphql-go-tools adds the field back and sets the value to null, however. graphql-go-tools should instead omit such fields.

    Here's an example that can be run in the examples/federation playground:

    query($skip:Boolean!){
      me {
        username
        reviews @skip(if:$skip) {
          author {
            username
          }
          body
          product {
            name 
          }
        }
      }
    }
    

    When $skip is true, this returns:

    {
      "data": {
        "me": {
          "username": "Me",
          "reviews": null
        }
      }
    }
    

    The expected result is instead:

    {
      "data": {
        "me": {
          "username": "Me",
        }
      }
    }
    

    Note that if the selection set only includes "reviews" (and not "username"), the GraphQL spec isn't clear about what to return. In this case Apollo returns:

    {
      "data": {
        "me": {}
      }
    }
    

    This issue is especially problematic when the skipped field is a non-null field. This is because the null bubbles up to the nearest nullable parent field, meaning all the other selection data is lost. (Also note that graphql-go-tools doesn't appear to add a resolve error in this case. I'll file another issue for that.)

  • Feature/ws init func

    Feature/ws init func

    Add possibility to check initial payload to see whether to accept the websocket connection. Example: authenticate user from websocket connection and save session of user in context.

  • proposal: Use HTTPClient interface instead of *http.Client instance

    proposal: Use HTTPClient interface instead of *http.Client instance

    We can use HTTPClient interface instead of *http.Client instance. For example, we have a custom httpclient that includes features such as our built-in security authentication certificate, retries, and circuit breaker. However, since this library uses *http.Client, this prevents us using these features. It is also very complicated to wrap the corresponding functionality. Currently, there is no use to more internals, interfaces is a very good solution.

    type HTTPClient interface{
        Do(*http.Request)(*http.Response, error)
    }
    
  • Impelement federation of interface fields

    Impelement federation of interface fields

    This is a big one. If a field has an interface type and the concrete types are federated, the query planner either ignores the field or generates an upstream operation that isn't valid.

    There appear to be (at least) three areas that need to be updated:

    • The EnterField method in the configuration visitor
    • The GraphQL data source code that generates the representations variable
    • The GraphQL data source code that adds fields to upstream operations

    The EnterField method -- non-child interface fields are skipped during query planning. This is because interface types aren't listed in root nodes; entity queries must be on concrete types (and therefore interfaces can't be entry points). (Note that interface fields owned by the same service work fine because interfaces appear in the list of child nodes.) This will be tricky. See "Interfaces and root fields" below.

    Representations -- __typename in the "representations" variable in the upstream operation is static. __typename needs to instead be set to the type of the input object.

    Upstream operation -- the inline fragment in an upstream operation is assumed to have same type as the parent object of the enclosing field. This isn't the case for interfaces! There needs to be a separate inline fragment for each concrete type that implements the interface. Note this also means that when fields are added they need to be added to each of the inline fragments. Furthermore, if the selection in the original operation contains inline fragments, the fields in these fragments need to be added to the appropriate concrete inline fragments.

    Interfaces and root fields:

    Although this doesn't align exactly with the spec, let's make the assumption that the same field on all the concrete types that implement an interface are resolved by the same service. For example, if the "id" field of type Dog implements Pet is resolved by the "pets" data source, then the "id" field of type Cat implements Pet is resolved by the "pets" data source as well. The federation spec is a bit vague about how federated interfaces work; it isn't clear how one would construct a set of schemas where the above wasn't true (where would the @external directive on the interface go?), so this solution seems good enough--or a least a good first step. It's certainly better than the current situation where federated interface fields don't work at all.

    How would this work?

    During planning (in the configurationVisitor EnterField method), if typeName is an interface and a planner isn't found with a child node for the typeName, fieldName pair, look up the first concrete type for the interface and set typeName to that. Then, a planner will be constructed based on the service that owns the field of the concrete type.

  • Fix federation directive variables

    Fix federation directive variables

    Fixes jensneuse/graphql-go-tools#316.

    When an entity query contains a field with a directive that includes a variable, the variable isn't added to constructed upstream operation. This PR makes it so variables used with directives are added as needed.

  • Pass WS initial payload from gateway to federated services

    Pass WS initial payload from gateway to federated services

    Hi, for my use case it's required to pass user related information (token/id/roles) from gateway to federated services. There are two possible options:

    1. Use payload in connection_init message (receiver must handle initPayload from connection_init message) (I prefer this option)
    2. Use Header field from FetchConfiguration (receiver must handle http header (before upgrading))

    Both options aren't ready to use, they require some contribution.

    @jensneuse which option would you prefer ?

  • IntrospectionQuery is a kind of reserved keyword

    IntrospectionQuery is a kind of reserved keyword

    Request using Apollo Rover

    curl --location --request POST 'http://localhost:8080/user/' \
    --header 'Content-Type: application/json' \
    --data-raw '{"variables":null,"query":"query IntrospectionQuery{\n    _service {\n        sdl\n    }\n}","operationName":"IntrospectionQuery"}'
    

    Response:

    {
        "data": {
            "__schema": {
                "queryType": {
                    "name": "Query"
                },
                "mutationType": null,
                "subscriptionType": null,
                "types": [
                    {
                        "kind": "SCALAR",
                        "name": "_Any",
                        "description": "",
                        "fields": [],
                        "inputFields": [],
                        "interfaces": [],
    ...
    

    Expected:

    {
        "data": {
            "_service": {
                "sdl": "extend type Query {\n  me: User\n}\n\ntype User @key(fields: \"id\") {\n  id: ID!\n  name: String\n  username: String\n}\n"
            }
        }
    }
    

    This is resolved by commenting out the following check inside IsIntrospectionQuery.

    // pkg/graphql/request.go:117
    if r.OperationName == defaultInrospectionQueryName {
        return true, nil
    }
    
  • Field transformations

    Field transformations

    First at all, thank you so much for this wonderful library. I was wondering if there’s a way to apply field transformations when using the execution engine v2, by looking at the code seems like it’s only supported in v1. What I like to achieve is to expose a field by composing to other fields name = firstName + lastName and I’d like to contribute this feature, but not sure if it makes sense and if the right approach would be to add a new option to the FieldConfiguration for the transformation using https://github.com/jensneuse/pipeline/pkg/pipe

  • graphql.Request.SetHeader doesn't work

    graphql.Request.SetHeader doesn't work

  • Extending an Interface-Type fails

    Extending an Interface-Type fails

    If a type which implements an interface is extended, the it won't resolve and additionally the key is cleared. I copied the federation example and modified it here, only the accounts-service has been modified:

    extend type Query {
      me: User
      self: Identity
    }
    
    interface Identity {
      id: ID!
    }
    
    type User implements Identity @key(fields: "id") {
      id: ID!
      username: String!
    }
    

    The query for "me" which returns the User shows the correct result:

    query {
      me {
        ... on User {
          id
          username
          reviews {
            body
          }
        }
      }
    }
    

    Result:

    {
      "data": {
        "me": {
          "id": "1234",
          "username": "Me",
          "reviews": [
            {
              "body": "A highly effective form of birth control."
            },
            {
              "body": "Fedoras are one of the most fashionable hats around and can look great with a variety of outfits."
            }
          ]
        }
      }
    }
    

    But the Query for "self" which returns the interface fails:

    query {
      self {
        id
        ... on User {
          uid: id
          username
          reviews {
            body
          }
        }
      }
    }
    

    Result:

    {
      "data": {
        "self": {
          "uid": "1234",
          "username": "Me"
        }
      }
    }
    
    • the id is not set
    • reviews aren't set, not even an empty array

    In the logs from the _entities are correctly queried.

    GraphQL Request {
      query($representations: [_Any!]!){_entities(representations: $representations){... on User {reviews {body}}}}
      var representations = [
        {
          "__typename": "User",
          "id": "1234"
        }
      ]
      resp: {
        "data": {
          "_entities": [
            {
              "reviews": [
                {
                  "body": "A highly effective form of birth control."
                },
                {
                  "body": "Fedoras are one of the most fashionable hats around and can look great with a variety of outfits."
                }
              ]
            }
          ]
        }
      }
    }
    
gqlanalysis makes easy to develop static analysis tools for GraphQL in Go.
gqlanalysis makes easy to develop static analysis tools for GraphQL in Go.

gqlanalysis gqlanalysis defines the interface between a modular static analysis for GraphQL in Go. gqlanalysis is inspired by go/analysis. gqlanalysis

Dec 14, 2022
A GraphQL complete example using Golang And PostgreSQL

GraphQL with Golang A GraphQL complete example using Golang & PostgreSQL Installation Install the dependencies go get github.com/graphql-go/graphql go

Dec 6, 2022
This app is an attempt towards using go lang with graphql data fetch in react front end.

go_movies _A React js + GraphQL supported with backend in GoLang. This app is an attempt towards using go lang with graphql data fetch in react front

Dec 7, 2021
An implementation of GraphQL for Go / Golang

graphql An implementation of GraphQL in Go. Follows the official reference implementation graphql-js. Supports: queries, mutations & subscriptions. Do

Dec 26, 2022
Convert Golang Struct To GraphQL Object On The Fly

Straf Convert Golang Struct To GraphQL Object On The Fly Easily Create GraphQL Schemas Example Converting struct to GraphQL Object type UserExtra stru

Oct 26, 2022
A collection of Go packages for creating robust GraphQL APIs

api-fu api-fu (noun) (informal) Mastery of APIs. ?? Packages The top level apifu package is an opinionated library that aims to make it as easy as pos

Dec 28, 2022
graphql parser + utilities

graphql utilities for dealing with GraphQL queries in Go. This package focuses on actually creating GraphQL servers and expects you to describe your s

Dec 20, 2022
GraphQL server with a focus on ease of use
GraphQL server with a focus on ease of use

graphql-go The goal of this project is to provide full support of the GraphQL draft specification with a set of idiomatic, easy to use Go packages. Wh

Dec 31, 2022
GraphQL server with a focus on ease of use
GraphQL server with a focus on ease of use

graphql-go The goal of this project is to provide full support of the GraphQL draft specification with a set of idiomatic, easy to use Go packages. Wh

Dec 25, 2022
GQLEngine is the best productive solution for implementing a GraphQL server 🚀

GQLEngine is the best productive solution for implementing a graphql server for highest formance examples starwars: https://github.com/gqlengine/starw

Apr 24, 2022
⚡️ A Go framework for rapidly building powerful graphql services

Thunder is a Go framework for rapidly building powerful graphql servers. Thunder has support for schemas automatically generated from Go types, live q

Dec 24, 2022
go generate based graphql server library
go generate based graphql server library

gqlgen What is gqlgen? gqlgen is a Go library for building GraphQL servers without any fuss. gqlgen is based on a Schema first approach — You get to D

Dec 31, 2022
Go monolith with embedded microservices including GRPC,REST,GraphQL and The Clean Architecture.
Go monolith with embedded microservices including GRPC,REST,GraphQL and The Clean Architecture.

GoArcc - Go monolith with embedded microservices including GRPC,REST, graphQL and The Clean Architecture. Description When you start writing a Go proj

Dec 21, 2022
GraphQL implementation for click house in Go.
GraphQL implementation for click house in Go.

clickhouse-graphql-go GraphQL implementation for clickhouse in Go. This package stores real time streaming websocket data in clickhouse and uses Graph

Nov 20, 2022
GraphQL parser comparison in different languages

graphql-parser-bench Parsing a schema or document can be a critical part of the application, especially if you have to care about latency. This is the

Jul 10, 2022
A simple Go, GraphQL, and PostgreSQL starter template

Simple Go/GraphQL/PostgreSQL template Purpose Have a good starting point for any project that needs a graphql, go, and postgres backend. It's a very l

Jan 8, 2022
proof-of-concept minimal GraphQL service for LTV

LTV GraphQL Proof-of-Concept This is a barebones proof-of-concept of a possible GraphQL implementation that interacts with Core. It includes a few ver

Jan 4, 2022
Learn GraphQL with THE IDOLM@STER SHINY COLORS.

faaaar Learn GraphQL with THE IDOLM@STER SHINY COLORS. Getting Started The following is a simple example which get information about 20-year-old idols

Dec 11, 2022
A simple (yet effective) GraphQL to HTTP / REST router

ReGraphQL A simple (yet effective) GraphQL to REST / HTTP router. ReGraphQL helps you expose your GraphQL queries / mutations as REST / HTTP endpoints

Dec 12, 2022