go-graphql-client

repository·master·Indexed 19 days ago

https://github.com/hasura/go-graphql-client

A GraphQL client for Go, forked from shurcooL/graphql, that adds support for named operations and subscriptions via WebSockets. It features a reflection-based query generator using Go structs, support for custom scalars, inline fragments, and configurable retry behavior. The library includes a SubscriptionClient following the Apollo client specification and provides utilities for executing raw queries via the Exec family of functions.

Tokens
11.6K
Snippets
51
Records
61
Agent score
66%

What's inside go-graphql-client

  1. Overview of go-graphql-client

    master

    go-graphql-client is a GraphQL client implementation for Go. It is a fork of shurcooL/graphql that includes extended features such as a subscription client and named operations.

    The subscription client follows the Apollo client specification using the WebSocket protocol via the coder/websocket library.

  2. Handle custom scalars and skipped fields

    master

    The client uses reflection to generate queries. To control this behavior, use specific struct tags:

    • Custom Scalars: If a field is a custom scalar (like a JSON object) and you want to prevent the generator from expanding its internal fields, add the scalar:"true" tag. If the type implements the JSON decoder interface, it will be automatically decoded.
    • Skip Fields: To prevent a field from being included in the generated GraphQL query, use the graphql:"-" tag.
    struct {
    	Viewer struct {
    		ID         interface{} `graphql:"-"` // Field skipped in query
    		Data       interface{} `scalar:"true"` // Field treated as scalar, not expanded
    		Login      string
    	}
    }
  3. Specify custom GraphQL type names

    master

    By default, the client infers the GraphQL type name from the Go type name. If you need a different name (e.g., for lowercase GraphQL types or private Go types), you can:

    1. Use a type alias.
    2. Implement the GetGraphQLType() string method on your type.
    type UserReviewInput struct {
    	Review string
    	UserID string
    }
    
    // Implement GetGraphQLType to override inference
    func (u UserReviewInput) GetGraphQLType() string {
        return "user_review_input"
    }
  4. Run client subscription examples

    master

    The repository provides two different client implementations for testing subscriptions with different protocols. You can run them using the following commands:

    • subscriptions-transport-ws protocol: go run ./client/subscriptions-transport-ws
    • graphql-ws protocol: go run ./client/graphql-ws
    # Subscription with subscriptions-transport-ws protocol
    go run ./client/subscriptions-transport-ws
    
    # Subscription with graphql-ws protocol
    go run ./client/graphql-ws
  5. Use GraphQL Subscriptions

    master

    Subscriptions are handled via a SubscriptionClient.

    1. Setup: Create a client with graphql.NewSubscriptionClient(url) and call client.Run() to start the loop.
    2. Subscribe: Use client.Subscribe(query, variables, callback) to listen for updates. The callback receives raw bytes and an error. Use jsonutil.UnmarshalGraphQL to decode the data.
    3. Unsubscribe: Use client.Unsubscribe(subscriptionId) or return graphql.ErrSubscriptionStopped from the callback to stop a subscription.
    4. Authentication: Use WithConnectionParams for connection-level parameters or WithWebSocketOptions to set HTTP headers (e.g., Authorization).
    client := graphql.NewSubscriptionClient("wss://example.com/graphql")
    defer client.Close()
    
    var subscription struct {
    	Me struct {
    		Name string
    	}
    }
    
    subId, err := client.Subscribe(&subscription, nil, func(dataValue []byte, errValue error) error {
    	if errValue != nil {
    		return nil
    	}
    	// Decode the response
    	err := jsonutil.UnmarshalGraphQL(dataValue, &subscription)
    	fmt.Println(subscription.Me.Name)
    	return nil
    })
    
    client.Run()
  6. Run the graphql-ws backwards compatibility example

    master

    This example demonstrates a subscription client interacting with a Node.js server that implements graphql-ws with subscriptions-transport-ws backwards compatibility. It also demonstrates custom authentication handling via HTTP headers.

    ### 1. Start the Server
    Requires Node.js and npm.
    
    ```bash
    cd server
    npm install
    npm start

    The server will be hosted on localhost:4000.

    2. Run the Client

    go run ./client
  7. Authenticate GraphQL requests

    master

    The graphql package does not handle authentication directly. Instead, you should provide an http.Client that performs authentication when creating a new client.

    For simple header injection, use the WithRequestModifier method. For standard OAuth2 flows, it is recommended to use the golang.org/x/oauth2 package to create an http.Client and pass it to graphql.NewClient.

    // Using WithRequestModifier for custom headers
    client := graphql.NewClient(endpoint, http.DefaultClient).
      WithRequestModifier(func(r *http.Request) {
    	  r.Header.Set("Authorization", "random-token")
    })
    
    // Using OAuth2
    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)
    }
  8. Debug GraphQL query generation

    master

    Since queries are generated at runtime via reflection, you can use Construct* functions to inspect the generated string before execution. Additionally, enabling debug mode with WithDebug will include the request and response details in the extensions[].internal property of any returned errors.

    // Inspect the generated query string
    queryStr, err := graphql.ConstructQuery(myStruct, variables)
    
    // Enable debug mode in client
    client := graphql.NewClient(endpoint, nil).WithDebug()
  9. Use inline fragments in queries

    master

    To handle GraphQL inline fragments (e.g., ... on Type), use the graphql struct tag on the field representing the fragment. You can either define the fragment fields directly in the parent struct or use an embedded struct type.

    // Option 1: Direct fields
    var q struct {
    	Hero struct {
    		Name  string
    		Droid struct {
    			PrimaryFunction string
    		} `graphql:"... on Droid"`
    	} `graphql:"hero(episode: \"JEDI\")"`
    }
    
    // Option 2: Embedded types
    type DroidFragment struct {
    	PrimaryFunction string
    }
    
    var q2 struct {
    	Hero struct {
    		Name          string
    		DroidFragment `graphql:"... on Droid"` 
    	} `graphql:"hero(episode: \"JEDI\")"`
    }