DEV Community

Cover image for Develop a Todo GraphQL Server in Golang πŸ”₯
Rahul Yarragodula
Rahul Yarragodula

Posted on

1 2

Develop a Todo GraphQL Server in Golang πŸ”₯

Introduction

This is a simple Todo application developed in Golang using GraphQL. This tutorial helps you to find the right way for building your own GraphQL Server implementation in Go.

Click here to access full source code

Click here to access the Postman collections.

In this tutorial we will focus mainly on 3 things

  • It's beginner friendly.
  • Focused on industry best practices.
  • Deploy to the cloud.

The scope of this tutorial is to focus mostly on building graphQL servers rather than Go basics. If you're completely new to Golang, I would highly encourage you to have a strong knowledge on Go basics before going into this article.

What is GraphQL ?

GraphQL is created by Facebook, implemented in their mobile app in 2012 and open-sourced in 2015.

GraphQL is a query language and server-side runtime for APIs. GraphQL provides a flexible and intuitive syntax that enables clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time.

As an alternative to REST, GraphQL lets developers construct requests that pull data from multiple data sources in a single API call. Therefore reducing the network calls and bandwidth that saves the battery life and CPU cycles consumed by the backend applications (Official source).

Additionally, GraphQL gives API maintainers the flexibility to add or deprecate fields without impacting existing queries.

GraphQL is rapidly becoming the standard for API-based data access.

What is Schemas ?

API developers create GraphQL schema that describes all the possible data that clients can query through that service. GraphQL schema is made up of object types, which defines the kind of object you can request and the fields it has.

The most common operations on any GraphQL APIs are Queries and Mutations. Queries is used for reading data from APIs. Mutations are used for Create, Update and Delete operations.

Each operation in GraphQL schema is attached to a function called resolvers. A resolver is used to perform the actual execution of Query or Mutation.

Live Demo

Use this endpoint to perform live test

Develop a GraphQL Server in Go

  • Open your favorite code editor and create a go module using this go mod init github.com/rahul-yr/learn-go-graphql command.
  • Install the below modules.
// used as web server
// you could use any web server of your choice such as mux, http, go fiber etc
go get github.com/gin-gonic/gin
// used for handling cors
go get github.com/gin-contrib/cors
// is the actual module for implementing GraphQL Server
go get github.com/graphql-go/graphql
view raw install.go hosted with ❀ by GitHub
  • Create a folder named todo in the root directory and 3 files named todo/models.go, todo/schema.go and todo/udf.go
  • Create a Todo model in the todo/models.go file. This model represents the todo item.
package todo
// Todo is a struct that represents a todo item
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
view raw models.go hosted with ❀ by GitHub

Implement CRUD operations.

  • Let's create the CRUD operations for the TODO model(todo/udf.go).
package todo
// used to store todos
var TodoItems = []Todo{}
// used to assign unique id to each todo
var count = 1
// Here comes all the Queries
// This method is used to get all todos
func GetTodos() []Todo {
return TodoItems
}
// This method is used to get a todo by id
// returns empty todo if not found
func GetTodo(id int) Todo {
for _, todo := range TodoItems {
if todo.ID == id {
return todo
}
}
return Todo{}
}
// Here comes all the Mutations
// This method is used to add a todo
// takes title as input and returns the todo
// returns the todo with assigned id
func AddTodo(title string) *Todo {
// Create a new todo
temp := &Todo{
ID: count,
Title: title,
Completed: false,
}
// Add it to the list of todos
TodoItems = append(TodoItems, *temp)
// Increment the count
count++
// Return the todo
return temp
}
// This method is used to update a todo
// takes id, title and completed as input
// returns true if updated successfully
// returns false if not found
func UpdateTodo(id int, title string, completed bool) bool {
for i, todo := range TodoItems {
if todo.ID == id {
TodoItems[i].Title = title
TodoItems[i].Completed = completed
return true
}
}
return false
}
// This method is used to delete a todo
// takes id as input
// returns true if deleted successfully
// returns false if not found
func DeleteTodo(id int) bool {
for i, todo := range TodoItems {
if todo.ID == id {
TodoItems = append(TodoItems[:i], TodoItems[i+1:]...)
return true
}
}
return false
}
view raw udf.go hosted with ❀ by GitHub
  • The above snippet has 5 functions defined 2 for Queries and 3 for Mutations. As well as a variable to store all the todo items(you could use a database instance here).
  • GetTodos() method is used for fetching all the todo items.
  • GetTodo(id int) method is used for fetching a todo item based on item id.
  • AddTodo(title string) method is used for creating a new todo item.
  • UpdateTodo(id int, title string, completed bool) method is used for updating the existing todo item based on item id.
  • DeleteTodo(id int) method is used for deleting the todo item based on item id.

Implement GraphQL schema

  • Now it's finally time to create the GraphQL schema (todo/schema.go).
package todo
import "github.com/graphql-go/graphql"
// used for Todo Schema
var todoSchema = graphql.NewObject(
graphql.ObjectConfig{
Name: "Todo",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.Int,
},
"title": &graphql.Field{
Type: graphql.String,
},
"completed": &graphql.Field{
Type: graphql.Boolean,
},
},
},
)
// used for Queries
var todoQueries = graphql.NewObject(
graphql.ObjectConfig{
Name: "TodoQueries",
Fields: graphql.Fields{
"todos": &graphql.Field{
Type: graphql.NewList(todoSchema),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return GetTodos(), nil
},
},
"todo": &graphql.Field{
Type: todoSchema,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.Int,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
id, ok := p.Args["id"].(int)
if ok {
return GetTodo(id), nil
}
return nil, nil
},
},
},
},
)
// used for Mutations
var todoMutations = graphql.NewObject(
graphql.ObjectConfig{
Name: "TodoMutations",
Fields: graphql.Fields{
"addTodo": &graphql.Field{
Type: todoSchema,
Args: graphql.FieldConfigArgument{
"title": &graphql.ArgumentConfig{
Type: graphql.String,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
title, ok := p.Args["title"].(string)
if ok {
return AddTodo(title), nil
}
return nil, nil
},
},
"updateTodo": &graphql.Field{
Type: graphql.Boolean,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.Int,
},
"title": &graphql.ArgumentConfig{
Type: graphql.String,
},
"completed": &graphql.ArgumentConfig{
Type: graphql.Boolean,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
id, ok := p.Args["id"].(int)
title, ok2 := p.Args["title"].(string)
completed, ok3 := p.Args["completed"].(bool)
if ok && ok2 && ok3 {
return UpdateTodo(id, title, completed), nil
}
return false, nil
},
},
"deleteTodo": &graphql.Field{
Type: graphql.Boolean,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.Int,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
id, ok := p.Args["id"].(int)
if ok {
return DeleteTodo(id), nil
}
return false, nil
},
},
},
},
)
// used for Root Schema
var TodoRootSchema, _ = graphql.NewSchema(
graphql.SchemaConfig{
Query: todoQueries,
Mutation: todoMutations,
},
)
view raw schema.go hosted with ❀ by GitHub
  • GraphQL schema is implemented using this graphql.NewSchema(graphql.SchemaConfig{...}) method. This method actually resolves the GraphQL requests.
  • You could define Queries,Mutations and other object types using this graphql.NewObject(graphql.Objectconfig{...}) method.
  • This graphql.Fields{...} method is useful for defining fields.
  • Below method is used for declaring input arguments.
graphql.FieldConfigArgument{
                    "some_id": &graphql.ArgumentConfig{
                        Type: graphql.SomeDataType,
                    },
                }
Enter fullscreen mode Exit fullscreen mode
  • Resolve block is where actual execution happens.

Implement an endpoint for GraphQL

  • Create a route handler(graph/router.go) file.
  • Here I have used the gin package. You could use any http package of your choice for creating endpoints.
  • The most important part here is to add this graphql.Do(...) method. This actually resolves the graphql request.
package graph
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/graphql-go/graphql"
"github.com/rahul-yr/learn-go-grapql/todo"
)
// var dummySchema, _ = graphql.NewSchema(
// graphql.SchemaConfig{
// Query: graphql.NewObject(graphql.ObjectConfig{
// Name: "RootQuery",
// }),
// Mutation: graphql.NewObject(graphql.ObjectConfig{}),
// },
// )
type RequestParams struct {
Query string `json:"query"`
Operation string `json:"operation"`
Variables map[string]interface{} `json:"variables"`
}
func TodoGraphRouter(c *gin.Context) {
var reqObj RequestParams
if err := c.ShouldBindJSON(&reqObj); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// perform cleanup and custom validations
// check authentication
// check authorization
// apply rate limiting
// apply tracing
// apply metrics
// apply logging
// perform business logic
result := graphql.Do(graphql.Params{
Context: c,
Schema: todo.TodoRootSchema,
RequestString: reqObj.Query,
VariableValues: reqObj.Variables,
OperationName: reqObj.Operation,
})
if len(result.Errors) > 0 {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": result.Errors})
return
} else {
c.JSON(http.StatusOK, result)
}
}
view raw router.go hosted with ❀ by GitHub

Run the server

  • Create a main.go file for running the graphql server.
package main
import (
"os"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/rahul-yr/learn-go-grapql/graph"
)
// main is the entry point for the application.
func main() {
// gin setup
gin.SetMode(gin.ReleaseMode)
// create gin engine
router := gin.Default()
// enable cors
router.Use(cors.Default())
// create graphql endpoint
router.POST("/todo", graph.TodoGraphRouter)
// add webserver port
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
// start webserver
router.Run(":" + port)
}
view raw main.go hosted with ❀ by GitHub
  • Run the server using go run . command
  • You can find the live postman api collection here. Refer this for api specifications.
  • Below are the few snapshots of API requests and responses in action using Postman.

Create a Todo item

create-todo.PNG

Update a Todo item

update-todo.PNG

Read a Todo item based on item id

read-todo-by-id.PNG

Read all Todo items

read-all-todos.PNG

Delete a Todo item

delete-todo.PNG

Deploy to Cloud

Know how to deploy this repo to Heroku ? Click here to find out right way

Summary

Awesome πŸ”₯, you have successfully completed this tutorial. I would πŸ’ to hear your feedback and comments on the great things you're gonna build with this. If you are struck somewhere feel free to comment. I am always available.
Please find the complete code at github

Image of Datadog

How to Diagram Your Cloud Architecture

Cloud architecture diagrams provide critical visibility into the resources in your environment and how they’re connected. In our latest eBook, AWS Solution Architects Jason Mimick and James Wenzel walk through best practices on how to build effective and professional diagrams.

Download the Free eBook

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay