DEV Community

murugan
murugan

Posted on

GraphQL with Golang

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
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

Open file tools.go and add the following content to it

package tools
import _ "github.com/99designs/gqlgen"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!
}

Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

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!")
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)