GraphQL with Golang
GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. GraphQL was developed internally by Facebook in 2012 before being publicly released in 2015
GraphQL is designed to make APIs fast, flexible, and developer-friendly. As an alternative to REST, GraphQL lets developers construct requests that pull data from multiple data sources in a single API call. GraphQL uses HTTP POST method to submit queries. We have a single service endpoint with variations in HTTP body, driven by schema and query patterns.
API developers will create a schema to define queries, data types, mutations that clients can query through
type Todo {
id: ID!
text: String!
done: bool
}
type Query {
todos: [Todo!]!
findTodor(id: String!): Todo
}
Consider a the above definition. We define Todo type, and what queries are exposed as a part of this API. When a query request comes-in, GraphQL resolver will validate the request against pre-defined schema and invokes connected methods for resolution. Another benefit to this approach is flexibility for clients to request fields they are interested in - It’s better for network, security and process. If we don’t want “lastName”, we don’t need to fetch it.
query {
todos {
id
text
done
}
}
Golang & GraphQL
Let's start with the bootstrap process.
mkdir gographql
cd gographql
go mod init gographql
go get github.com/99designs/gqlgen
mkdir tools && cd tools
touch tools.go
cd ..
code .
Open file tools.go and add the following content to it
package tools
import _ "github.com/99designs/gqlgen"
Save your changes in VSCode and back to terminal. Let's now generate sample graphql schema and golang connectors to execute
go run github.com/99designs/gqlgen init
Creating gqlgen.yml
Creating graph/schema.graphqls
Creating server.go
Generating…
go: downloading gopkg.in/check.v1 v1.0.0–20190902080502–41f04d3bba15
go: downloading github.com/kr/pretty v0.1.0
go: downloading github.com/kr/text v0.1.0
Exec "go run ./server.go" to start GraphQL server
You should now see a bunch of files added.
- model/model_gen.go golang data types for graphql types created in schema.graphqls
- generated/generated.go this is a file with generated code that injects context and middleware for each query and mutation
- schema.graphqls a GraphQL schema file where types, queries, and mutations are defined.
- schema.resolvers.go a go file with wrapper code for queries and mutations defined in schema.graphqls
If you want make any changes in the schema.graphqls do the changes and delete the schema.resolvers.go and run the following command
go run github.com/99designs/gqlgen generate
Back to terminal and run the following comments
go run ./server.go
2022/06/22 18:20:03 connect to http://localhost:8080/ for GraphQL playground
Head over to http://localhost:8080. You’ll see the playground.
Let’s introduce a custom schema and see how it works. Let’s say we want to have a simple CRUD ops on Todo model. Nothing complicated in Todo model — Id, Text and Done should be good for us to start.
Head over to “schema.graphqls” replace file content with the following
type Todo {
id: ID!
text: String!
done: Boolean!
todo: Todo!
}
type Todo {
id: ID!
text: String!
}
type Query {
todos: [Todo!]!
findTodo(id: String!): Todo
}
input NewTodo {
text: String!
Id: String!
}
input DeleteTodo {
Id: String!
}
type Mutation {
createTodo(input: NewTodo!): Todo!
removeTodo(input: DeleteTodo!): Todo!
}
- We have defined a Todo type
- Couple of queries — One to list all todos and other to search for todo
- Input types — Types for mutation requests.
- Mutation — Capability to modify data/model
To proceed, delete "schema.resolvers.go" and head back to terminal
go run github.com/99designs/gqlgen generate
You should not see any error(s). New “schema.resolvers.go” file should be generated.
// CreateTodo is the resolver for the createTodo field.
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
log.Println("Create a new Todo")
uuidValue := uuid.NewString()
todo := &model.Todo{ID: uuidValue, Text: input.Text, Done: true}
todos = append(todos, todo)
return todo, nil
}
// RemoveTodo is the resolver for the removeTodo field.
func (r *mutationResolver) RemoveTodo(ctx context.Context, input model.DeleteTodo) (*model.Todo, error) {
index := -1
for i, todo := range todos {
if todo.ID == input.ID {
index = i
}
}
if index == -1 {
return nil, errors.New("Cannot find todo you are looking for!")
}
todo := todos[index]
todos = append(todos[:index], todos[index+1:]...)
return todo, nil
}
// Todos is the resolver for the todos field.
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
return todos, nil
}
// FindTodo is the resolver for the findTodo field.
func (r *queryResolver) FindTodo(ctx context.Context, id string) (*model.Todo, error) {
panic(fmt.Errorf("not implemented: FindTodo - findTodo"))
}
// Mutation returns MutationResolver implementation.
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
// Query returns QueryResolver implementation.
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
// !!! WARNING !!!
// The code below was going to be deleted when updating resolvers. It has been copied here so you have
// one last chance to move it out of harms way if you want. There are two reasons this happens:
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean.
func (r *queryResolver) Todo(ctx context.Context, id string) (*model.Todo, error) {
panic(fmt.Errorf("not implemented: Todo - todo"))
}
var todos []*model.Todo
func init() {
log.Println("Init - Todo array to be created")
todos = make([]*model.Todo, 0)
todos = append(todos, &model.Todo{ID: "1", Text: "Hello One", Done: true, Todo: &model.Todo{Text: "Text1"}})
todos = append(todos, &model.Todo{ID: "2", Text: "Hello Two", Done: true, Todo: &model.Todo{Text: "Text2"}})
todos = aid:ppend(todos, &model.Todo{ID: "3", Text: "Hello Three", Done: true, Todo: &model.Todo{Text: "Text3"}})
log.Println("Init - Todo array has been created")
}
func (r *queryResolver) FindTodos(ctx context.Context, id string) (*model.Todo, error) {
for _, todo := range todos {
if todo.ID == id {
return todo, nil
}
}
return nil, errors.New("Cannot find todo you are looking for!")
}
Run the server.go and and see how the application works using below queries
query{
todos{
id
text
done
}
}
Find Todo
query{
findTodo(id:"1"){
id
text
done
}
}
Create Todo
mutation createTodo($todo: NewTodo!){
createTodo(input:$todo){
id
text
done
}
}
Query Variable:
{
"todo": {
"id":4,
"text": "Hello 4",
"done": true
}
}
Reference:
Top comments (0)