Home Development of Websites REST Server Development in Go.Part 7: GraphQL

REST Server Development in Go.Part 7: GraphQL

by admin

This is the seventh (final) part of a series of articles about developing REST servers in Go. In the previous articles we’ve dealt mostly with different approaches to developing a REST API for a simple task management automation application. Today we’ll explore something completely new and talk about how to make a similar API using GraphQL rather than REST.
REST Server Development in Go.Part 7: GraphQL

Previous parts : REST server development in Go. Part 1: the standard library
REST server development in Go. Part 2: applying the gorilla/mux router
REST server development in Go. Part 3: using the Gin web framework
REST server development in Go. Part 4: using OpenAPI and Swagger
REST Server Development in Go. Part 5: Middleware.
REST server development in Go. Part 6: authentication
Are you here – REST Server Development in Go. Part 7: GraphQL
While I pay some attention here to the reasons for choosing GraphQL and comparing GraphQL and REST, that’s not the main focus here. There are many articles covering these issues and I encourage you to look them up and read them. The main purpose of this article is to give you an example of creating a GraphQL server in Go. To keep it simple, this server uses a data model very similar to the one implemented in one of the previous articles (it’s a simple task list backend).

Why do we need GraphQL technology?

Let’s turn to Wikipedia : "GraphQL is an API query language for working with APIs and also an environment for executing queries.
Recall the database for storing task information from first material. It’s very simple. So in order for us to have a real reason to use GraphQL, we should approximate this database to something that can be used in real projects. Let’s add a set of attachments to each task. Now the corresponding types in Go will look like this :

type Attachmentstruct {Name string `json:"Name"`Date time.Time`json:"Date"`Contents string `json:"Contents"`}type Taskstruct {ID int `json:"Id"`Text string `json:"Text"`Tags []string `json:"Tags"`Due time.Time `json:"Due"`Attachments []*Attachment `json:"Attachments"`

And if you’re more comfortable working with database schematics, here’s one.
REST Server Development in Go.Part 7: GraphQL
Database schema
Line 1 ---- * between tables indicates, in relational database terms, a one-to-many relationship. This means that multiple attachments can be associated with a single task.
So far our REST API has looked like this :

POST /task/ : creates a task and returns its IDGET /task/<taskid>: returns a task with an IDGET /task/ : returns all tasksDELETE /task/<taskid> : deletes a task with IDGET /tag/<tagname>: returns the list of tasks with the specified tagGET /due/<yy> /<mm> /<dd> : returns the list of tasks scheduled on a specified date

One thing to note here is that many of these queries return a list of tasks. Let’s imagine that now, as described above, tasks have attachments. And these attachments can be quite large. Under the new conditions, in response to a query designed to retrieve tasks by tag ( GET /tag/<tagname> ), a lot of data may come back that, if, for example, the client only wants the names of the tasks, will be redundant. This is far from ideal, especially when working with our API over a slow or expensive internet connection (for example, if the list of tasks is displayed on a mobile device). This is the problem that common REST APIs suffer from, when they get more data than needed in response to a request (over-fetching).
To solve this problem, we often use an approach which, in our case, in response to the query GET /tag/<tagname> would only return task IDs, not the tasks themselves. Then, having those IDs, the client can list them and, to get the needed tasks at once, run the queries GET /task/<taskid> True, even with this approach, the client will get some redundant data, because it may not need all the task data, which also includes attachments. So here we can return only the names (or IDs) of attachments and equip our API with one more endpoint, which returns attachments by their IDs.
But now we face the opposite problem, which is that not enough data is returned in response to the query (under-fetching). It turns out that instead of getting the necessary information by making just one request, we have to make a whole bunch of requests. Perhaps one query for each task with the corresponding tag, and one more for each task attachment. This approach has its own problems, such as increasing the time needed to get all the necessary data from the server.

How does GraphQL solve the problems of redundant and insufficient data retrieval?

Let’s think for a moment about how we can solve the problems described above. Obviously, you can create a special endpoint in the REST API that returns exactly what we need. For example, it could look like GET /task-name-and-attachment-name-in-tag/<tagname> and would return a list of task names with a list of attachment names for each of those tasks. It turns out that with this approach there is neither a lack nor an excess of data! This is a perfectly workable approach, some REST APIs have specialized endpoints like this one. But the problem with such endpoints is obvious: they are not flexible. Suppose we need the same data, but we are going to get it without using a tag. For example, we need to get the same list of tasks scheduled for a given date. Would we have to create a new endpoint, returning the same data, but now with a selection by date? It’s certainly possible, but it should be pretty obvious that as the API gets more complex, it’s going to have lots of endpoints duplicating each other’s functionality to varying degrees.
Another possibility is that the API would have a single endpoint to which more complex queries could be sent. These queries, which are a bit like SQL queries, would contain exactly what kind of data we want.
This is exactly how GraphQL works. I’ll come back to the comparison of this technology with SQL, but for now let’s talk about how it’s implemented in GraphQL. Here is a GraphQL query that can be sent by a client :

query {getTasksByTag(tag: "shopping") {TextAttachments{Name}}}

In response to this request, the client will get a list of tasks. But for each task, only the text description of the task and the names of all its attachments will be returned. And this is exactly what we need. And all this comes to us in response to a single request.

Our Go server with GraphQL

I took the server code from previous articles and rewrote it to use GraphQL. The data model has been updated to take into account the use of attachments. Here You can find the full server code here.
There are several packages designed to work with GraphQL in Go projects. In this experiment I decided to use gqlgen The package takes the GraphQL schema as input and generates Go-code that implements an HTTP server to serve requests for the relevant data. Handlers (called resolvers in GraphQL jargon) are blanks which are the responsibility of the programmer. To gqlgen prepared worthy guide , so we will focus on how the server works.
Here is the GraphQL schema for our backend (these schemas are made in a language whose rules are enshrined in specifications ):

type Query{getAllTasks: [Task]getTask(id: ID!): TaskgetTasksByTag(tag: String!): [Task]getTasksByDue(due: Time!): [Task]}type Mutation{createTask(input: NewTask!): Task!deleteTask(id: ID!): BooleandeleteAllTasks: Boolean}scalar Timetype Attachment {Name: String!Date: Time!Contents: String!}type Task{Id: ID!Text: String!Tags: [String!]Due: Time!Attachments: [Attachment!]}input NewAttachment{Name: String!Date: Time!Contents: String!}input NewTask{Text: String!Tags: [String!]Due: Time!Attachments: [NewAttachment!]}

There are a few details to pay attention to here :

  • Query and Mutation – are special GraphQL types. They describe the API. The other types are also of some interest. GraphQL is strictly typed! This is very good because it allows you to more clearly describe how the input data is validated (the JSON format commonly used in REST APIs is much looser typed).
  • Even though the APIs described in type Query , seems to return [Task] which is a list of tasks, there is no problem getting redundant data here. As you can see from the query example in the previous section, GraphQL allows customers to accurately describe the fields they are looking for in their queries and as a result, only these fields are returned in the query response.
  • GraphQL doesn’t have built-in types for values representing time and date, but you can create extensions when working with GraphQL. Here I’m using the extension scalar Time extension that is built into gqlgen. The library binds this construct to the Go type time.Time

Finally, it’s hard not to substitute the types NewTask and NewAttachment which duplicate the types Task and Attachment Why do we need them? The answer to this question turns out to be quite complex. GraphQL types can represent graphs, in our case the point is that one task can have multiple attachments. But in theory, the same nesting can belong to more than one task. This may be the reason why "GraphQL" has the word "graph" in its name (I’d be happy to know if I’m wrong in this assumption), and this is very different from the way relational databases are designed.
Such graph data can be difficult to construct with the expectation that they will be used in the input parameters. What about when they are mutually recursive? As a result, GraphQL uses a strict separation – the types that can be used to design input data must be clearly labeled. They can only represent trees, not graphs. As a result, although we could, in theory, reuse the type Task to describe the input parameters (since it is not a graph), GraphQL forbids this and insists on using the new type.
This is how I organized the work on this project :

  1. Executed the command go run github.com/99designs/gqlgen init
  2. Prepared the GraphQL schema described above.
  3. Executed the command go run github.com/99designs/gqlgen generate
  4. Edited the generated code by implementing the recognizers.
  5. Ran the generated server.go

Speaking of recognizers, gqlgen creates an empty type struct called Resolver , which is used to define the handler methods. This structure should be tailored to the needs of the application and include the common data needed by all the resolvers. In our case, this is just the task repository :

type Resolver struct {Store *taskstore.TaskStore}

The gqlgen package also generates empty handler methods whose code we need to write. For most recognizers, this is very simple code. For example this :

func (r *queryResolver) GetAllTasks(ctx context.Context) ([]*model.Task, error) {return r.Store.GetAllTasks(), nil}

Note that our recognizer returns a complete list of tasks. GraphQL’s ability to select specific fields (or protect against redundant data) is implemented in the code generated by gqlgen. As far as I understand, the recognizer has no way of knowing which fields to return in response to a specific query, so we always need to take whatever we can from the database. Maybe this is a limitation of gqlgen, or maybe it’s a feature of all GraphQL servers – I don’t know exactly. If you describe it in SQL language, you’ll find that we’re always asked to execute a query like select * from ... , not a query targeting specific fields. After our recognizer returns this data to the GraphQL engine, the engine will send only the fields that the client needs in response to the query.
Finally, gqlgen generates the function main in the file server.go , the code of which we can edit :

func main() {port := os.Getenv("PORT")if port == "" {port = defaultPort}resolver := graph.Resolver{Store: taskstore.New(), }srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: resolver}))http.Handle("/", playground.Handler("GraphQL playground", "/query"))http.Handle("/query", srv)log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)log.Fatal(http.ListenAndServe(":"+port, nil))}

Here handler.NewDefaultServer – this is the GraphQL-server, it is registered on the path /query We can add more paths here, for example – you can even mix REST and GraphQL.

Interactive environment for experiments with GraphQL-server

One of the great features of GraphQL is the interactive playground which makes experimenting with the server from the browser a very nice and easy task. In the above server code you can see the registration playground.Handler designed to serve the root path. It gives access to a one-page application called graphql-playground . Here’s what it looks like.
REST Server Development in Go.Part 7: GraphQL
Appendix graphql-playground
As you can see, I, in the left pane, entered the query I described above, and after it was executed, the data returned by the server was displayed in the right pane. The interactive environment supports syntax highlighting, autocomplete input, allows you to open multiple tabs, and even knows how to "translate" queries into commands curl which will be useful if you are planning to use such commands in test scripts.

Comparison of GraphQL and REST

If you analyze just the big picture, GraphQL has obvious advantages over REST. Particularly in the area of efficiency, in situations where too much or too little data can make working with REST APIs less than optimal. But this flexibility of GraphQL isn’t free. Namely, it allows to send very complex and arbitrary GraphQL queries to servers without much trouble, which means organizing DoS-attacks. However, of course, there are recommendations that can help GraphQL-server developers to avoid such troubles.
GraphQL is a developing technology. Along with it a lot of interesting tools are developing. For example the interactive environment described above. But REST has been around much longer and GraphQL can hardly compete with it in terms of w.r.t.-tools and versatility. For example, almost any server these days has a REST API, and many tools have been developed for monitoring, logging, profiling, and various kinds of research of such APIs. REST technology, moreover, is very simple – we’re talking about common paths that are accessed via HTTP. They can often be accessed with simple curl constructs or from a browser. The GraphQL technology is more complicated because the texts of GraphQL requests must be placed in the bodies of POST requests.
The simplicity of REST has far-reaching consequences. In a typical web backend, REST queries are served by SQL queries (often non-trivial ones) sent to databases. Those who use GraphQL also have to use a special query language that in some ways is a bit like SQL and in other ways very different from SQL, because the graph model GraphQL is based on does not really correspond to the model relational databases are based on. Experience tells me that when using GraphQL, you have to keep more information in mind than when using SQL. Specifically, we’re talking about the GraphQL query and how it "translates" to the language of the relational database underlying the server’s storage system. Prospective projects like. Dgraph (a database originally designed for GraphQL, with a graph-based backend) and PostGraphile (a system that automatically builds correspondences between GraphQL and PostgreSQL) are interesting developments worth keeping an eye on for those interested in GraphQL.
Another thing to pay attention to is caching. REST gets along well with HTTP caching, because this technology is mostly based on idempotent GET requests. GraphQL, on the other hand, is not so well off in this sense, since this system, at the HTTP level, does not distinguish between idempotent data retrieval requests and data modification requests.
In the end, we can say that programmers are either restricted by what systems they need to interact with, or (in rare pleasant cases) have the ability to choose technologies for new projects. In the latter case, you just need to choose the tool that best suits the specific task. And the wider the choice, the better.
Previous parts : REST server development in Go. Part 1: the standard library
REST server development in Go. Part 2: applying the gorilla/mux router
REST server development in Go. Part 3: using the Gin web framework
REST server development in Go. Part 4: using OpenAPI and Swagger
REST Server Development in Go. Part 5: Middleware.
REST server development in Go. Part 6: authentication
Are you here – REST Server Development in Go. Part 7: GraphQL
Do you use GraphQL when creating Go servers?
REST Server Development in Go.Part 7: GraphQL

You may also like