Giter VIP home page Giter VIP logo

go-graphql's Introduction

go-graphql

Go Reference

Package graphql provides a Client for talking to a GraphQL server using HTTP with JSON-encoded requests/responses.

Inspired by shurcooL/graphQL but includes the following enhancements:

  1. Maps Golang primitive data types to GraphQL types (variable values).
  2. Returns structured error types that reflect GraphQL-level errors (for error handling / backoff).
  3. Returns HTTP response status/headers (for error handling / backoff).

Installation

go get github.com/jbrekelmans/go-graphql

Usage

Construct a GraphQL client, specifying the GraphQL server URL. Then, you can use it to make GraphQL queries and mutations.

client := graphql.NewClient("https://example.com/graphql", nil)
// Use client...

Authentication

Some GraphQL servers may require authentication. The graphql package does not directly handle authentication. Instead, when creating a new client, you're expected to pass an http.Client that performs authentication. The easiest and recommended way to do this is to use the golang.org/x/oauth2 package. You'll need an OAuth token with the right scopes. Then:

import "golang.org/x/oauth2"

func main() {
	src := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: os.Getenv("GRAPHQL_TOKEN")},
	)
	httpClient := oauth2.NewClient(context.Background(), src)

	client := graphql.NewClient("https://example.com/graphql", httpClient)
	// Use client...

Simple Query

To make a GraphQL query, you need to define a corresponding Go type.

For example, to make the following GraphQL query:

query {
	me {
		name
	}
}

You can define this variable:

var query struct {
	Me struct {
		Name string
	}
}

Then call client.Query, passing a pointer to it:

_, err := client.Query(context.Background(), &query, nil)
if err != nil {
	// Handle error.
}
fmt.Println(query.Me.Name)

// Output: Luke Skywalker

Arguments and Variables

Often, you'll want to specify arguments on some fields. You can use the graphql struct field tag for this.

For example, to make the following GraphQL query:

{
	human(id: "1000") {
		name
		height(unit: METER)
	}
}

You can define this variable:

var q struct {
	Human struct {
		Name   string
		Height float64 `graphql:"height(unit: METER)"`
	} `graphql:"human(id: \"1000\")"`
}

Then call client.Query:

_, err := client.Query(context.Background(), &q, nil)
if err != nil {
	// Handle error.
}
fmt.Println(q.Human.Name)
fmt.Println(q.Human.Height)

// Output:
// Luke Skywalker
// 1.72

However, that'll only work if the arguments are constant and known in advance. Otherwise, you will need to make use of variables. Replace the constants in the struct field tag with variable names:

var q struct {
	Human struct {
		Name   string
		Height float64 `graphql:"height(unit: $unit)"`
	} `graphql:"human(id: $id)"`
}

Then, define a variables map with their values:

variables := map[string]interface{}{
	"id":   graphql.ID(id),
	"unit": starwars.LengthUnit("METER"),
}

Finally, call client.Query providing variables:

_, err := client.Query(context.Background(), &q, variables)
if err != nil {
	// Handle error.
}

Inline Fragments

Some GraphQL queries contain inline fragments. You can use the graphql struct field tag to express them.

For example, to make the following GraphQL query:

{
	hero(episode: "JEDI") {
		name
		... on Droid {
			primaryFunction
		}
		... on Human {
			height
		}
	}
}

You can define this variable:

var q struct {
	Hero struct {
		Name  string
		Droid struct {
			PrimaryFunction string
		} `graphql:"... on Droid"`
		Human struct {
			Height float64
		} `graphql:"... on Human"`
	} `graphql:"hero(episode: \"JEDI\")"`
}

Alternatively, you can define the struct types corresponding to inline fragments, and use them as embedded fields in your query:

type (
	DroidFragment struct {
		PrimaryFunction string
	}
	HumanFragment struct {
		Height float64
	}
)

var q struct {
	Hero struct {
		Name          string
		DroidFragment `graphql:"... on Droid"`
		HumanFragment `graphql:"... on Human"`
	} `graphql:"hero(episode: \"JEDI\")"`
}

Then call client.Query:

_, err := client.Query(context.Background(), &q, nil)
if err != nil {
	// Handle error.
}
fmt.Println(q.Hero.Name)
fmt.Println(q.Hero.PrimaryFunction)
fmt.Println(q.Hero.Height)

// Output:
// R2-D2
// Astromech
// 0

Mutations

Mutations often require information that you can only find out by performing a query first. Let's suppose you've already done that.

For example, to make the following GraphQL mutation:

mutation($ep: Episode!, $review: ReviewInput!) {
	createReview(episode: $ep, review: $review) {
		stars
		commentary
	}
}
variables {
	"ep": "JEDI",
	"review": {
		"stars": 5,
		"commentary": "This is a great movie!"
	}
}

You can define:

var m struct {
	CreateReview struct {
		Stars      int
		Commentary string
	} `graphql:"createReview(episode: $ep, review: $review)"`
}
variables := map[string]interface{}{
	"ep": starwars.Episode("JEDI"),
	"review": starwars.ReviewInput{
		Stars:      5,
		Commentary: "This is a great movie!",
	},
}

Then call client.Mutate:

_, err := client.Mutate(context.Background(), &m, variables)
if err != nil {
	// Handle error.
}
fmt.Printf("Created a %v star review: %v\n", m.CreateReview.Stars, m.CreateReview.Commentary)

// Output:
// Created a 5 star review: This is a great movie!

Error Handling

Error handling is needed to:

  1. Be a good citizen and back off on rate limits.

  2. Retry on transient errors when reliability is important.

Error handling should be implemented by handling one (or more) of the following cases.

import "errors"

...

resp, err := client.Query(ctx, &q, nil)
if resp != nil && resp.StatusCode/100 == 5 {
    // 5xx error
}
var gerr *graphql.Error
if errors.As(err, &gerr) && len(gerr.Errors) > 0 && gerr.Errors[0].Message == "invalid value" {
    // we passed an invalid value in the query
}

// For completeness:
if terr := (interface{Timeout() bool})(nil); errors.As(err, &terr) && terr.Timeout() {
    // timeout produced by HTTP client/transport/dial/DNSLookup
}
if terr := (interface{Temporary() bool})(nil); errors.As(err, &terr) && terr.Temporary() {
    // "temporary" error produced by HTTP client/transport/dial/DNSLookup
}

Acknowledgements

License

go-graphql's People

Contributors

jbrekelmans avatar brekelj1 avatar

Watchers

 avatar

go-graphql's Issues

Scalar types in selections do not marshal properly.

Scalar types in query/mutation types do not marshal properly.
For example, when using ID type in a query, the following error was observed from GitHub's GraphQL API:
Selections can't be made on scalars (field 'id' returns ID but has selections [s])

Scalar types in inputs do work properly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.