Golang Graphql Tutorial with Examples


GO

Author: Tuan Nguyen
Reviewer: Deepak Prasad

Introduction to Golang Graphql

In this tutorial, we will examine some examples of graphql in Golang. GraphQL is an alternative to REST, created by Facebook (http://graphql.org/). This technology allows a server to implement and publish a schema, and the clients then can ask for the information they need, rather than understanding and making use of various API endpoints.

At its simplest, GraphQL is about asking for specific fields on objects. Let's start by looking at a very simple query and the result we get when we run it:

{
  hero {
    name
  }
}

Result: 

{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

You can see immediately that the query has exactly the same shape as the result. This is essential to GraphQL, because you always get back what you expect, and the server knows exactly what fields the client is asking for.

 

A simple example of using graphql package

In this example, we will use graphql package, an implementation of GraphQL in Go. To install the library, run:

go get github.com/graphql-go/graphql

The following is a simple example which defines a schema with a single hello string-type field and a Resolve method which returns the string "Welcome to GoLinuxcloud!". A GraphQL query is performed against this schema with the resulting output printed in JSON format.

package main

import (
	"encoding/json"
	"fmt"
	"log"

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

func main() {
	// Schema
	fields := graphql.Fields{
		"hello": &graphql.Field{
			Type: graphql.String,
			Resolve: func(p graphql.ResolveParams) (interface{}, error) {
				return `Welcome to GoLinuxcloud!`, nil
			},
		},
	}

	// Define a GraphQL object type named rootQuery
	rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
	schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
	schema, err := graphql.NewSchema(schemaConfig)
	if err != nil {
		log.Fatalf("failed to create new schema, error: %v", err)
	}

	// Query
	query := `
		{
			hello
		}
	`
	params := graphql.Params{Schema: schema, RequestString: query}
	r := graphql.Do(params)
	if len(r.Errors) > 0 {
		log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
	}
	rJSON, _ := json.Marshal(r)
	fmt.Printf("%s \n", rJSON)
}

Output:

{"data":{"hello":"Welcome to GoLinuxcloud"}}

 

Reading with thread's parameter from context

Here's an example of reading the thread's parameter from context. We will send a query within a context, and try to read a parameter from that context.

package main

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

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

func main() {
	extractFieldFromContextFn := func(p graphql.ResolveParams) (interface{}, error) {
		return p.Context.Value(p.Args["key"]), nil
	}

	schema, err := graphql.NewSchema(graphql.SchemaConfig{
		Query: graphql.NewObject(graphql.ObjectConfig{
			Name: "Query",
			Fields: graphql.Fields{
				"value": &graphql.Field{
					Type: graphql.String,
					Args: graphql.FieldConfigArgument{
						"key": &graphql.ArgumentConfig{Type: graphql.String},
					},
					Resolve: extractFieldFromContextFn,
				},
			},
		}),
	})

	if err != nil {
		log.Fatalf("failed to create new schema, error: %v", err)
	}

	query := `{ value(key:"User1") }`

	result := graphql.Do(graphql.Params{
		Schema:        schema,
		RequestString: query,
		Context:       context.WithValue(context.TODO(), "User1", "xyz"),
	})

	rJSON, _ := json.Marshal(result)
	fmt.Printf("%s \n", rJSON)
}

Output:

{"data":{"value":"xyz"}} 

In the query, we send a key. Instead of return a fixed string like example 1, in this example the ResolveParams() returns the value of key in the context:

query := `{ value(key:"User1") }`
Context:       context.WithValue(context.TODO(), "User1", "xyz")
{"data":{"value":"xyz"}} 

 

GraphQL server application with http

Set up helper function, struct and data file

First lets define business logic that will expose two methods ListStudents, GetStudent and Student type as below:

var students []*Student

type Student struct {
	ID     int
	Name   string
	Passed bool
}

func ListStudents() ([]*Student, error) {
	return students, nil
}

func GetStudent(id int) (*Student, error) {
	for _, s := range students {
		if s.ID == id {
			return s, nil
		}
	}
	return nil, errors.New("Todo not found")
}

All students are saved in a JSON file called data.json and are loaded when the application starts:

[
    {
      "id": 1,
      "name": "Dan",
      "passed": true
    },
    {
        "id": 2,
        "name": "Bob",
        "passed": true
    },
    {
        "id": 3,
        "name": "Daniel",
        "passed": true
    },
    {
        "id": 4,
        "name": "Erik",
        "passed": true
    },
    {
        "id": 5,
        "name": "Ben",
        "passed": true
    },
    {
        "id": 6,
        "name": "Chuck",
        "passed": true
    }
]

Load the students data.json file:

	content, err := ioutil.ReadFile("./data.json")
	if err != nil {
		fmt.Print("Error:", err)

	}

	err = json.Unmarshal(content, &students)

	if err != nil {
		fmt.Print("Error:", err)
	}

 

GraphQL Schema

First, we'll create an object schema type to represent our Student struct. This is accomplished using the NewObject function from the graphql-go/graphql package.

	studentType := graphql.NewObject(graphql.ObjectConfig{
		Name: "Student",
		Fields: graphql.Fields{
			"id": &graphql.Field{
				Type: graphql.Int,
				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
					if student, ok := p.Source.(*Student); ok {
						return student.ID, nil
					}
					return nil, nil
				},
			},
			"name": &graphql.Field{
				Type: graphql.String,
				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
					if student, ok := p.Source.(*Student); ok {
						return student.Name, nil
					}
					return nil, nil
				},
			},
			"passed": &graphql.Field{
				Type: graphql.Boolean,
				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
					if student, ok := p.Source.(*Student); ok {
						return student.Passed, nil
					}
					return nil, nil
				},
			},
		},
	})

This object will have three fields id, name and passed with data type correspond to our Student struct's field respectively.

The next step is to define the query schema. For the purposes of this example application, we will expose two queries named students and student, the first of which will list all students and the second of which will retrieve a specific student by providing an id as a query argument.

queryType := graphql.NewObject(graphql.ObjectConfig{
		Name: "Query",
		Fields: graphql.Fields{
			"students": &graphql.Field{
				Type: graphql.NewList(studentType),
				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
					return ListStudents()
				},
			},
			"student": &graphql.Field{
				Type: studentType,
				Args: graphql.FieldConfigArgument{
					"id": &graphql.ArgumentConfig{
						Type: graphql.NewNonNull(graphql.Int),
					},
				},
				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
					return GetStudent(p.Args["id"].(int))
				},
			},
		},
	})

As you can see, the actual logic for getting the students list is by calling our server methods defined in the preceding section in the resolve function of each query: ListStudents and GetStudent

 

Connect GraphQL with HTTP

Last but not least, the graphql endpoint should be accessible via http. We could write our own http server implementation to convert request and response back and forth, but there is a package called graphql-go/handler that has already done that for us, so let's use it.

	h := handler.New(&handler.Config{
		Schema:     &schema,
		Pretty:     true,
		GraphiQL:   false,
		Playground: true,
	})

The Playground option is only for development purposes, and it allows us to experiment with the query once we navigate to http://localhost:8080/graphql

The complete code:

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/graphql-go/graphql"
	"github.com/graphql-go/handler"
)

var students []*Student

type Student struct {
	ID     int
	Name   string
	Passed bool
}

func ListStudents() ([]*Student, error) {
	return students, nil
}

func GetStudent(id int) (*Student, error) {
	for _, s := range students {
		if s.ID == id {
			return s, nil
		}
	}
	return nil, errors.New("Todo not found")
}

func main() {
	content, err := ioutil.ReadFile("./data.json")
	if err != nil {
		fmt.Print("Error:", err)

	}

	err = json.Unmarshal(content, &students)

	if err != nil {
		fmt.Print("Error:", err)
	}

	studentType := graphql.NewObject(graphql.ObjectConfig{
		Name: "Student",
		Fields: graphql.Fields{
			"id": &graphql.Field{
				Type: graphql.Int,
				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
					if student, ok := p.Source.(*Student); ok {
						return student.ID, nil
					}
					return nil, nil
				},
			},
			"name": &graphql.Field{
				Type: graphql.String,
				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
					if student, ok := p.Source.(*Student); ok {
						return student.Name, nil
					}
					return nil, nil
				},
			},
			"passed": &graphql.Field{
				Type: graphql.Boolean,
				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
					if student, ok := p.Source.(*Student); ok {
						return student.Passed, nil
					}
					return nil, nil
				},
			},
		},
	})

	queryType := graphql.NewObject(graphql.ObjectConfig{
		Name: "Query",
		Fields: graphql.Fields{
			"students": &graphql.Field{
				Type: graphql.NewList(studentType),
				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
					return ListStudents()
				},
			},
			"student": &graphql.Field{
				Type: studentType,
				Args: graphql.FieldConfigArgument{
					"id": &graphql.ArgumentConfig{
						Type: graphql.NewNonNull(graphql.Int),
					},
				},
				Resolve: func(p graphql.ResolveParams) (interface{}, error) {
					return GetStudent(p.Args["id"].(int))
				},
			},
		},
	})

	schema, err := graphql.NewSchema(graphql.SchemaConfig{
		Query: queryType,
	})

	if err != nil {
		log.Fatal(err)
	}

	h := handler.New(&handler.Config{
		Schema:     &schema,
		Pretty:     true,
		GraphiQL:   false,
		Playground: true,
	})

	http.Handle("/graphql", h)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

The final step is to open up the endpoint and experiment with it, as shown in the image below:

Query:

Golang Graphql Tutorial with Examples

Response:

Golang Graphql Tutorial with Examples

 

Summary

In this tutorial, I defined a GraphQL schema and query it in Golang. For more complex examples, refer to the examples graphql_test.go.

 

References

https://github.com/graphql-go/graphql
https://graphql.org/

 

Tuan Nguyen

Tuan Nguyen

He is proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment