{"id":20005,"date":"2020-11-24T15:42:50","date_gmt":"2020-11-24T14:42:50","guid":{"rendered":"https:\/\/www.inovex.de\/blog\/?p=20005"},"modified":"2025-01-22T08:53:25","modified_gmt":"2025-01-22T07:53:25","slug":"graphql-application-golang-tutorial","status":"publish","type":"post","link":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/","title":{"rendered":"Building a GraphQL Example Application with Golang [Tutorial]"},"content":{"rendered":"<p>Most of the people reading this are probably familiar with using and implementing REST interfaces to manage data exchange between automated processes. It is a tried and tested option which has more than ample support in many programming languages. Not only that but it has been around long enough to teach us a few lessons on what we need to look out for when we try to implement data-exchange interfaces that should be easy to maintain and evolve over time. Some of the lessons are encompassed in a new type of API language called <strong>GraphQL<\/strong>\u00a0, which was initially developed by Facebook and later gained much popularity and support from the open-source community.<!--more--><\/p>\n<p>There\u2019s quite a bit to unpack when talking about GraphQL but I will only underline some of the major features since it&#8217;s not the scope of this article to go in-depth:<\/p>\n<ul>\n<li>type safety; this means that there is also integrated validation for incoming and outgoing data<\/li>\n<li>integrated schema introspection; that means you get the luxury of checking out what the API can do without needing any special software, you just open up the GraphQL Playground and read through (it can also contain documentation); this of course has the added advantage of being able to automatically generate consumer clients<\/li>\n<li>selective data retrieval; you limit the amount of query data to only what you need<\/li>\n<li>integrated realtime updates via subscriptions<\/li>\n<\/ul>\n<p>In order to get you familiar with some of the basics, what we are going to do is to try and get an example Todo application running, written in Golang using the <a href=\"https:\/\/github.com\/99designs\/gqlgen\">99designs\/gqlgen<\/a>\u00a0library\/framework. We\u2019re going to extend the API bit by bit to cover some basics of getting data exposed through GraphQL.<\/p>\n<p>NOTE: in this example I used <strong>go 1.15.<\/strong><\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_83 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\"><p class=\"ez-toc-title\" style=\"cursor:inherit\"><\/p>\n<\/div><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#Why-99designsgqlgen-and-not-graphql-gographql\" >Why\u00a099designs\/gqlgen and not graphql-go\/graphql?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#Getting-Started\" >Getting Started<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#Returning-Some-Todos-in-the-GraphQL-Interface\" >Returning Some Todos in the GraphQL Interface<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#Add-New-Query-and-Mutation\" >Add New Query and Mutation<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#Adding-a-Subscription\" >Adding a Subscription<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#Creating-Custom-Resolvers-for-Individual-Fields\" >Creating Custom Resolvers for Individual Fields<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#Conclusion\" >Conclusion<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Why-99designsgqlgen-and-not-graphql-gographql\"><\/span>Why\u00a099designs\/gqlgen and not graphql-go\/graphql?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>At the time of writing this article the <em>99designs\/gqlgen<\/em> library has around 5k Github stars vs 6.9k stars for <em>graphql-go\/graphql<\/em>, and the latter is most likely the first link you\u2019ll find when you Google for \u201cgo graphql\u201c. While the number of Github stars is only a loose indication of how popular a library is, in this case both of them have a fairly similar amount.<\/p>\n<p>Despite that, I find <em>99designs\/gqlgen<\/em> to be the better option as it brings 3 great features in comparison to <em>graphql-go\/graphql<\/em>, which are all listed in the very first paragraphs of the README file:<\/p>\n<ul>\n<li>you can define your schema directly in <em>*.graphqls<\/em> definition files<\/li>\n<li>the schema bindings are generated from the <em>*.graphqls<\/em> definition files, you only need to provide your business logic; no manual bootstrapping required<\/li>\n<li>for each type defined in <em>*.graphqls<\/em> files a corresponding Go type is generated; that means we have the benefit of type safety (NOTE: you can also map to Go types written by yourself if you need more control)<\/li>\n<\/ul>\n<p>In contrast, with <em>graphql-go\/graphql\u00a0<\/em><\/p>\n<ul>\n<li>the schema is only programmatically defined which, from personal experience, can quickly become very hard to understand even with smaller schemas; just take a look at the <a href=\"https:\/\/github.com\/graphql-go\/graphql\/blob\/master\/testutil\/testutil.go\">star wars example<\/a><\/li>\n<li>you often use <code>map[string]interface{}<\/code> or <code>interface{}<\/code> for type definitions, which is very vulnerable to human-error and potential bugs<\/li>\n<\/ul>\n<p>What\u2019s cool is that <em>99designs\/gqlgen<\/em> also delivers the GraphQL Playground by default. For <em>graphql-go\/graphql<\/em> you need an <a href=\"https:\/\/github.com\/graphql-go\/handler\">extra library<\/a>.\u00a0One last point that I do find important is that <em>99designs\/gqlgen<\/em> has a significantly higher number of contributors (154 vs 83 from <em>graphql-go\/graphql<\/em>).<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Getting-Started\"><\/span>Getting Started<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Getting a minimal example going is fairly easy. Just create a project directory and run the following commands inside it.<\/p>\n<pre>go mod init graphql-go-example\r\n\r\ngo get github.com\/99designs\/gqlgen #current version is 0.13.0\r\n\r\ngo run github.com\/99designs\/gqlgen init<\/pre>\n<p>This creates the following tree structure, which encompasses a simple <strong>todo-management application<\/strong>:<\/p>\n<pre class=\"lang:sh decode:true\">$ tree -L 3 .\r\n\r\n.\r\n\r\n\u251c\u2500\u2500 go.mod\r\n\r\n\u251c\u2500\u2500 go.sum\r\n\r\n\u251c\u2500\u2500 gqlgen.yml\r\n\r\n\u251c\u2500\u2500 graph\r\n\r\n\u2502 \u251c\u2500\u2500 generated\r\n\r\n\u2502 \u2502 \u2514\u2500\u2500 generated.go\r\n\r\n\u2502 \u251c\u2500\u2500 model\r\n\r\n\u2502 \u2502 \u2514\u2500\u2500 models_gen.go\r\n\r\n\u2502 \u251c\u2500\u2500 resolver.go\r\n\r\n\u2502 \u251c\u2500\u2500 schema.graphqls\r\n\r\n\u2502 \u2514\u2500\u2500 schema.resolvers.go\r\n\r\n\u2514\u2500\u2500 server.go<\/pre>\n<p>gqlgen stores configuration in <em>gqlgen.yml<\/em> and uses it to customize the generation of the schema bindings. It\u2019s worth taking a look through it to understand what aspects of the schema binding generation can be changed, but I\u2019ll point out the following settings:<\/p>\n<pre class=\"lang:yaml decode:true\">schema:\r\n\r\n- graph\/*.graphqls\r\n\r\nexec:\r\n\r\nfilename: graph\/generated\/generated.go\r\n\r\npackage: generated\r\n\r\nmodel:\r\n\r\nfilename: graph\/model\/models_gen.go\r\n\r\npackage: model\r\n\r\n...<\/pre>\n<p>From this we get that files with the *.graphqls extension are interpreted as schema files and used to generate the bindings and types under graph\/.<\/p>\n<ul>\n<li><code>graph\/generated\/generated.go<\/code> is where all the \u201cglue\u201c between the graphql specification and go is generated; you\u2019ll hopefully never need to look inside<\/li>\n<li><code>graph\/model\/models_gen.go<\/code> is where the unmanaged types and fields are generated<\/li>\n<li><code>graph\/resolver.go<\/code> is where we will add our dependencies, which will be shared between the different types of resolvers<\/li>\n<li><code>graph\/schema.resolvers.go<\/code> contains the go receivers for the resolvers which correspond to the queries and mutations defined in <code>schema.graphqls<\/code><\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"Returning-Some-Todos-in-the-GraphQL-Interface\"><\/span>Returning Some Todos in the GraphQL Interface<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The default-generated go receivers for the query- and mutationResolver in <code>schema.resolvers.go<\/code> are just placeholders and throw panics. Let&#8217;s replace that with a simple in-memory list management:<\/p>\n<p><em>graph\/resolver.go<\/em><\/p>\n<pre class=\"lang:go decode:true\">\/\/we add a constructor method that instantiates the Todo and User slice\u00a0\r\n\r\nfunc NewResolver() *Resolver {\r\n\r\n  users := make([]*model.User, 0)\r\n\r\n  users = append(users, &amp;model.User{ID: \"1\", Name: \"fphilip\"})\r\n\r\n  users = append(users, &amp;model.User{ID: \"2\", Name: \"lturanga\"})\r\n\r\n  return &amp;Resolver{\r\n\r\n    todos: make([]*model.Todo, 0),\r\n\r\n    users: users,\r\n\r\n  }\r\n\r\n}\r\n\r\ntype Resolver struct {\r\n\r\n  todos\u00a0 \u00a0 \u00a0 []*model.Todo\r\n\r\n  users\u00a0 \u00a0 \u00a0 []*model.User\r\n\r\n  lastTodoId int\r\n\r\n}<\/pre>\n<p><em>server.go<\/em><\/p>\n<pre class=\"lang:go decode:true\">\/\/instead of \u2018generated.Config{Resolvers: &amp;graph.Resolver{}})\u2019\r\n\r\nsrv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: graph.NewResolver()}))<\/pre>\n<p><em>graph\/schema.resolvers.go<\/em><\/p>\n<pre class=\"lang:go decode:true\">func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {\r\n\r\n  var targetUser *model.User\r\n\r\n  for _, user := range r.users {\r\n\r\n    if user.ID == input.UserID {\r\n\r\n      targetUser = user\r\n\r\n      break\r\n\r\n    }\r\n\r\n  }\r\n\r\n  if targetUser == nil {\r\n\r\n    return nil, fmt.Errorf(\"user with id='%s' not found\", input.UserID)\r\n\r\n  }\r\n\r\n  newTodo := &amp;model.Todo{\r\n\r\n    ID: \u00a0 strconv.Itoa(r.lastTodoId),\r\n\r\n    Text: input.Text,\r\n\r\n    Done: false,\r\n\r\n    User: targetUser,\r\n\r\n  }\r\n\r\n  r.todos = append(r.todos, newTodo)\r\n\r\n  r.lastTodoId++\r\n\r\n  return newTodo, nil\r\n\r\n}\r\n\r\nfunc (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {\r\n\r\n  return r.todos, nil\r\n\r\n}<\/pre>\n<p>Now we\u2019ve also got a functional example. Start the application using<\/p>\n<pre>go run server.go<\/pre>\n<p>and access http:\/\/localhost:8080 . You\u2019ll get the embedded GraphQL Playground which has some nice features like automatic schema introspection and autofill.<\/p>\n<p>Quick intro to the GraphQL Playground: in the left panel you write the requests and on the right side you get the response. You can also use tabs.<\/p>\n<p>Let\u2019s get the current list of todos by executing the query:<\/p>\n<pre class=\"lang:default decode:true \">{\r\n\r\n\u00a0 todos {\r\n\r\n\u00a0 \u00a0 id\r\n\r\n\u00a0 \u00a0 text\r\n\r\n\u00a0 \u00a0 done\r\n\r\n\u00a0 \u00a0 user {\r\n\r\n\u00a0 \u00a0 \u00a0 id\r\n\r\n\u00a0 \u00a0 \u00a0 name\r\n\r\n\u00a0 \u00a0 }\r\n\r\n\u00a0 }\r\n\r\n}<\/pre>\n<p>For that we get, as expected, an empty list<\/p>\n<pre class=\"lang:default decode:true \">{\r\n\r\n\u00a0 \"data\": {\r\n\r\n\u00a0 \u00a0 \"todos\": []\r\n\r\n\u00a0 }\r\n\r\n}<\/pre>\n<p>Let\u2019s not leave it at that and add some entries using the <code>createTodo<\/code> mutation.<\/p>\n<pre class=\"lang:default decode:true \">mutation {\r\n\r\n\u00a0 createTodo(input:{text:\"get flowers for Leela\", userId:\"1\"}) {\r\n\r\n\u00a0 \u00a0 id\r\n\r\n\u00a0 \u00a0 text\r\n\r\n\u00a0 \u00a0 done\r\n\r\n\u00a0 \u00a0 user {\r\n\r\n\u00a0 \u00a0 \u00a0 id\r\n\r\n\u00a0 \u00a0 \u00a0 name\r\n\r\n\u00a0 \u00a0 }\r\n\r\n\u00a0 }\r\n\r\n}<\/pre>\n<p>The entry gets a new id which we can see in the response<\/p>\n<pre class=\"lang:default decode:true \">{\r\n\r\n\u00a0 \"data\": {\r\n\r\n\u00a0 \u00a0 \"createTodo\": {\r\n\r\n\u00a0 \u00a0 \u00a0 \"id\": \"0\",\r\n\r\n\u00a0 \u00a0 \u00a0 \"text\": \"get flowers for Leela\",\r\n\r\n\u00a0 \u00a0 \u00a0 \"done\": false,\r\n\r\n\u00a0 \u00a0 \u00a0 \"user\": {\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 \"id\": \"1\",\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 \"name\": \"fphilip\"\r\n\r\n\u00a0 \u00a0 \u00a0 }\r\n\r\n\u00a0 \u00a0 }\r\n\r\n\u00a0 }\r\n\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Add-New-Query-and-Mutation\"><\/span>Add New Query and Mutation<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We can add a new todo entity and we can list all existing todos. But this was with the generated query and mutation from the gqlgen <em>init<\/em> process we executed in the beginning. Let\u2019s make our own, this time for user entities.<\/p>\n<p>The first thing is to adapt the GraphQL schema:<\/p>\n<pre class=\"lang:default decode:true\">type Query {\r\n\r\n\u00a0 todos: [Todo!]!\r\n\r\n\u00a0 users: [User!]!\r\n\r\n}\r\n\r\n...\r\n\r\ninput NewUser {\r\n\r\n\u00a0 name: String!\r\n\r\n}\r\n\r\ntype Mutation {\r\n\r\n\u00a0 createTodo(input: NewTodo!): Todo!\r\n\r\n\u00a0 createUser(input: NewUser!): User!\r\n\r\n}\r\n\r\n<\/pre>\n<p>We added a new <code>users<\/code> query and a <code>createUser<\/code> mutation, which needs a corresponding input object.<\/p>\n<p>This, by itself, is not enough yet, since we need to regenerate the go bindings based off of our new schema, which is done by executing:<\/p>\n<pre class=\"lang:sh decode:true\">go run github.com\/99designs\/gqlgen generate\r\n\r\n<\/pre>\n<p>gqlgen then takes care of putting all the components together so we can extend our functionality. The new model which corresponds to the <code>NewUser<\/code> input is generated with the same name and the methods corresponding to the new query and mutation are in <code>schema.resolvers.go<\/code>:<\/p>\n<pre class=\"lang:go decode:true\">func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {\r\n\r\n  panic(fmt.Errorf(\"not implemented\"))\r\n\r\n}\r\n\r\nfunc (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {\r\n\r\n  panic(fmt.Errorf(\"not implemented\"))\r\n\r\n}<\/pre>\n<p>Everything else we wrote before in schema.resolvers.go stays untouched. That&#8217;s great!<\/p>\n<p>So now let&#8217;s update the code to perform the same kind of management as with the todos.<\/p>\n<p><i>resolver.go<\/i><\/p>\n<pre class=\"lang:go decode:true\">func NewResolver() *Resolver {\r\n\r\n  users := make([]*model.User, 0)\r\n\r\n  users = append(users, &amp;model.User{ID: \"1\", Name: \"fphilip\"})\r\n\r\n  users = append(users, &amp;model.User{ID: \"2\", Name: \"lturanga\"})\r\n\r\n  return &amp;Resolver{\r\n\r\n    todos: make([]*model.Todo, 0),\r\n\r\n    users: users,\r\n\r\n    lastUserId: 3,\r\n\r\n  }\r\n\r\n}\r\n\r\ntype Resolver struct {\r\n\r\n  todos\u00a0 \u00a0 \u00a0 []*model.Todo\r\n\r\n  users\u00a0 \u00a0 \u00a0 []*model.User\r\n\r\n  lastTodoId int\r\n\r\n  lastUserId int\r\n\r\n}<\/pre>\n<p><i>schema.resolvers.go<\/i><\/p>\n<pre class=\"lang:go decode:true\">func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {\r\n\r\n  newUser := &amp;model.User{\r\n\r\n    ID: \u00a0 strconv.Itoa(r.lastUserId),\r\n\r\n    Name: input.Name,\r\n\r\n  }\r\n\r\n  r.users = append(r.users, newUser)\r\n\r\n  r.lastUserId++\r\n\r\n  return newUser, nil\r\n\r\n}\r\n\r\nfunc (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {\r\n\r\n  return r.users, nil\r\n\r\n}<\/pre>\n<p>Now let\u2019s start our server again and see what we get when we add a new user using the Playground:<\/p>\n<pre class=\"lang:default decode:true\">\/\/request\r\n\r\nmutation {\r\n\r\n\u00a0 createUser(input:{name:\"brodriguez\"}) {\r\n\r\n\u00a0 \u00a0 id\r\n\r\n\u00a0 \u00a0 name\r\n\r\n\u00a0 }\r\n\r\n}\r\n\r\n\/\/response\r\n\r\n{\r\n\r\n\u00a0 \"data\": {\r\n\r\n\u00a0 \u00a0 \"createUser\": {\r\n\r\n\u00a0 \u00a0 \u00a0 \"id\": \"3\",\r\n\r\n\u00a0 \u00a0 \u00a0 \"name\": \"brodriguez\"\r\n\r\n\u00a0 \u00a0 }\r\n\r\n\u00a0 }\r\n\r\n}<\/pre>\n<p>Yay, this means we get the new entry also when querying all users:<\/p>\n<pre class=\"lang:default decode:true\">\/\/request\r\n\r\n{\r\n\r\n\u00a0 users {\r\n\r\n\u00a0 \u00a0 id\r\n\r\n\u00a0 \u00a0 name\r\n\r\n\u00a0 }\r\n\r\n}\r\n\r\n\/\/response\r\n\r\n{\r\n\r\n\u00a0 \"data\": {\r\n\r\n\u00a0 \u00a0 \"users\": [\r\n\r\n\u00a0 \u00a0 \u00a0 {\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 \"id\": \"1\",\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 \"name\": \"fphilip\"\r\n\r\n\u00a0 \u00a0 \u00a0 },\r\n\r\n\u00a0 \u00a0 \u00a0 {\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 \"id\": \"2\",\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 \"name\": \"lturanga\"\r\n\r\n\u00a0 \u00a0 \u00a0 },\r\n\r\n\u00a0 \u00a0 \u00a0 {\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 \"id\": \"3\",\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 \"name\": \"brodriguez\"\r\n\r\n\u00a0 \u00a0 \u00a0 }\r\n\r\n\u00a0 \u00a0 ]\r\n\r\n\u00a0 }\r\n\r\n}<\/pre>\n<p>So let\u2019s break it down:<\/p>\n<ul>\n<li>we started by adapting the schema file<\/li>\n<li>the go bindings were regenerated using <em>gqlgen<\/em> which takes care of not disturbing anything you already wrote<\/li>\n<li>we adapted the new methods corresponding to the new query and mutation<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Adding-a-Subscription\"><\/span>Adding a Subscription<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>If you thought what we did until now was easy, then you\u2019ll be surprised how much easier it is to add support for subscriptions.\u00a0Let\u2019s say we wanted to offer the consumers of the GraphQL interface the possibility of seeing in realtime which users are added.<\/p>\n<p><i>schema.graphqls<\/i><\/p>\n<pre class=\"lang:default decode:true\">type Subscription {\r\n\r\n\u00a0 userAdded: User!\r\n\r\n}<\/pre>\n<p>We regenerate the go bindings, just like before, and <em>gqlgen<\/em> creates a whole new resolver type dedicated just for subscriptions, with a single receiver called <code>UserAdded<\/code>:<\/p>\n<pre class=\"lang:go decode:true\">func (r *subscriptionResolver) UserAdded(ctx context.Context) (&lt;-chan *model.User, error) {\r\n\r\n  panic(fmt.Errorf(\"not implemented\"))\r\n\r\n}<\/pre>\n<p>The first thing to notice is that the return type for the receiver method is a go channel. That&#8217;s because the framework is asking for the channel on which it will asynchronously start listening to for events where the payload is an instance of <code>*model.User<\/code>. Each instance sent on this channel will be distributed to all active subscription clients.<\/p>\n<pre class=\"lang:go decode:true\">func (r *subscriptionResolver) UserAdded(ctx context.Context) (&lt;-chan *model.User, error) {\r\n\r\n  return r.usersChan, nil\r\n\r\n}<\/pre>\n<p>Of course we also have to instantiate the channel:<\/p>\n<p><i>resolver.go<\/i><\/p>\n<pre class=\"lang:go decode:true\">func NewResolver() *Resolver {\r\n\r\n  ...\r\n\r\n  return &amp;Resolver{\r\n\r\n    ...\r\n\r\n    usersChan: make(chan *model.User),\r\n\r\n  }\r\n\r\n}\r\n\r\ntype Resolver struct {\r\n\r\n  ...\r\n\r\n  usersChan chan *model.User\r\n\r\n}<\/pre>\n<p>The only thing left to do is to take care that, when a new user is added, we send the instance on the channel and the framework will take care of the rest.<\/p>\n<p><i>schema.resolvers.go<\/i><\/p>\n<pre class=\"lang:go decode:true\">func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {\r\n\r\n  ...\r\n\r\n  r.usersChan &lt;- newUser\r\n\r\n  return newUser, nil\r\n\r\n}<\/pre>\n<p>To see this in action, we go back to the Playground and start the subscription, which creates the connection in the background :<\/p>\n<pre class=\"lang:default decode:true\">subscription {\r\n\r\n\u00a0 userAdded {\r\n\r\n\u00a0 \u00a0 id\r\n\r\n\u00a0 \u00a0 name\r\n\r\n\u00a0 }\r\n\r\n}<\/pre>\n<p>Now anytime someone calls the <code>createUser<\/code> mutation, the active subscription connection will get a copy of that new user.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Creating-Custom-Resolvers-for-Individual-Fields\"><\/span>Creating Custom Resolvers for Individual Fields<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>So far gqlgen has resolved the value of the individual entity fields by making a 1:1 mapping with the fields with the same name from the generated Go structs. i.e. if we ask for the value of <em>Todo.id<\/em> entity, we\u2019ll get the value from the <em>Todo.ID<\/em> go struct.<\/p>\n<p>During development however we may come across use cases where we find that it is better to lazy-load some of the data which can be retrieved from an entity. After all, keep in mind that a consumer of the GraphQL interface can decide to only retrieve a part of the entity data.<\/p>\n<p>Let\u2019s say we need to also deliver the length of the todos text.<\/p>\n<p><i>schema.graphqls<\/i><\/p>\n<pre class=\"lang:default decode:true\">type Todo {\r\n\r\n\u00a0 ...\r\n\r\n\u00a0 textLength: Int!\r\n\r\n}<\/pre>\n<p>If we regenerate the bindings with just these schema changes, the framework will extend the <code>Todo<\/code> go struct with the textLength field, which we will need to populate every time an instance of the entity is delivered. But for the sake of this example let\u2019s say that the operation to determine the text length is CPU intensive enough to warrant a lazy-loading approach. For that, we need to tell the framework we want to compute the value only if the data is explicitly asked for. In technical terms, this translates to configuring the value of the textLength field to be computed in a dedicated resolver.<\/p>\n<p><i>gqlgen.yml<\/i><\/p>\n<pre class=\"lang:yaml decode:true\">...\r\n\r\nmodels:\r\n\r\n\u00a0 Todo:\r\n\r\n\u00a0 \u00a0 fields:\r\n\r\n\u00a0 \u00a0 \u00a0 textLength:\r\n\r\n\u00a0 \u00a0 \u00a0 \u00a0 resolver: true\r\n\r\n...<\/pre>\n<p>Regenerating the go bindings at this point gives us a new receiver which will do the \u201cheavy-lifting\u201c of computing the length of text from a todo:<\/p>\n<pre class=\"lang:go decode:true\">func (r *todoResolver) TextLength(ctx context.Context, obj *model.Todo) (int, error) {\r\n\r\n  return len(obj.Text), nil\r\n\r\n}<\/pre>\n<p>But lazy-loading is of course not the only use case. It can also be used for caching results, retry mechanisms and anything that might fit your use-case.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We\u2019ve covered some solid basics of implementing a GraphQL interface using golang. There are other topics that can be explored but the intention of the article is to keep it scoped to beginners. What was not covered in this article are some features around type definitions in GraphQL like scalars, unions, interfaces, fragments etc. but they are very well explained in the official documentation. Make sure to check out the documentation from <a href=\"https:\/\/gqlgen.com\/feature-comparison\/\">gqlgen<\/a>\u00a0to see if there are differences between the official specification and what the library supports.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most of the people reading this are probably familiar with using and implementing REST interfaces to manage data exchange between automated processes. It is a tried and tested option which has more than ample support in many programming languages. Not only that but it has been around long enough to teach us a few lessons [&hellip;]<\/p>\n","protected":false},"author":201,"featured_media":20218,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"ep_exclude_from_search":false,"footnotes":""},"tags":[68,356,357],"service":[425],"coauthors":[{"id":201,"display_name":"Gabriel-Mihai Ruiu","user_nicename":"gruiu"}],"class_list":["post-20005","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-backend","tag-go","tag-graphql","service-backend"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Building a GraphQL Example Application with Golang [Tutorial] - inovex GmbH<\/title>\n<meta name=\"description\" content=\"This article teaches you the solid basics of implementing a GraphQL interface using golang and the 99designs\/gqlgen library.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a GraphQL Example Application with Golang [Tutorial] - inovex GmbH\" \/>\n<meta property=\"og:description\" content=\"This article teaches you the solid basics of implementing a GraphQL interface using golang and the 99designs\/gqlgen library.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/\" \/>\n<meta property=\"og:site_name\" content=\"inovex GmbH\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/inovexde\" \/>\n<meta property=\"article:published_time\" content=\"2020-11-24T14:42:50+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-01-22T07:53:25+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/11\/graphql-golang-todo-list.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Gabriel-Mihai Ruiu\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/11\/graphql-golang-todo-list-1024x576.png\" \/>\n<meta name=\"twitter:creator\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:site\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Gabriel-Mihai Ruiu\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"12\u00a0Minuten\" \/>\n\t<meta name=\"twitter:label3\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data3\" content=\"Gabriel-Mihai Ruiu\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/\"},\"author\":{\"name\":\"Gabriel-Mihai Ruiu\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/ee62ffe3b2170d28a8900df059124599\"},\"headline\":\"Building a GraphQL Example Application with Golang [Tutorial]\",\"datePublished\":\"2020-11-24T14:42:50+00:00\",\"dateModified\":\"2025-01-22T07:53:25+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/\"},\"wordCount\":1750,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2020\\\/11\\\/graphql-golang-todo-list.png\",\"keywords\":[\"Backend\",\"go\",\"graphql\"],\"articleSection\":[\"Applications\",\"English Content\",\"General\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/\",\"name\":\"Building a GraphQL Example Application with Golang [Tutorial] - inovex GmbH\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2020\\\/11\\\/graphql-golang-todo-list.png\",\"datePublished\":\"2020-11-24T14:42:50+00:00\",\"dateModified\":\"2025-01-22T07:53:25+00:00\",\"description\":\"This article teaches you the solid basics of implementing a GraphQL interface using golang and the 99designs\\\/gqlgen library.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2020\\\/11\\\/graphql-golang-todo-list.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2020\\\/11\\\/graphql-golang-todo-list.png\",\"width\":1920,\"height\":1080,\"caption\":\"graphql golang todo Liste\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/graphql-application-golang-tutorial\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building a GraphQL Example Application with Golang [Tutorial]\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"name\":\"inovex GmbH\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\",\"name\":\"inovex GmbH\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"width\":1921,\"height\":1081,\"caption\":\"inovex GmbH\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/inovexde\",\"https:\\\/\\\/x.com\\\/inovexgmbh\",\"https:\\\/\\\/www.instagram.com\\\/inovexlife\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/inovex\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UC7r66GT14hROB_RQsQBAQUQ\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/ee62ffe3b2170d28a8900df059124599\",\"name\":\"Gabriel-Mihai Ruiu\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4e249b88efe8d06a30e9aa7ede100b0411127e2164c3a424d08cce2f9cb65120?s=96&d=retro&r=gb9f4999c23470399b1bb1d5e7b073040\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4e249b88efe8d06a30e9aa7ede100b0411127e2164c3a424d08cce2f9cb65120?s=96&d=retro&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4e249b88efe8d06a30e9aa7ede100b0411127e2164c3a424d08cce2f9cb65120?s=96&d=retro&r=g\",\"caption\":\"Gabriel-Mihai Ruiu\"},\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/author\\\/gruiu\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Building a GraphQL Example Application with Golang [Tutorial] - inovex GmbH","description":"This article teaches you the solid basics of implementing a GraphQL interface using golang and the 99designs\/gqlgen library.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/","og_locale":"de_DE","og_type":"article","og_title":"Building a GraphQL Example Application with Golang [Tutorial] - inovex GmbH","og_description":"This article teaches you the solid basics of implementing a GraphQL interface using golang and the 99designs\/gqlgen library.","og_url":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/","og_site_name":"inovex GmbH","article_publisher":"https:\/\/www.facebook.com\/inovexde","article_published_time":"2020-11-24T14:42:50+00:00","article_modified_time":"2025-01-22T07:53:25+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/11\/graphql-golang-todo-list.png","type":"image\/png"}],"author":"Gabriel-Mihai Ruiu","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/11\/graphql-golang-todo-list-1024x576.png","twitter_creator":"@inovexgmbh","twitter_site":"@inovexgmbh","twitter_misc":{"Verfasst von":"Gabriel-Mihai Ruiu","Gesch\u00e4tzte Lesezeit":"12\u00a0Minuten","Written by":"Gabriel-Mihai Ruiu"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#article","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/"},"author":{"name":"Gabriel-Mihai Ruiu","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/ee62ffe3b2170d28a8900df059124599"},"headline":"Building a GraphQL Example Application with Golang [Tutorial]","datePublished":"2020-11-24T14:42:50+00:00","dateModified":"2025-01-22T07:53:25+00:00","mainEntityOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/"},"wordCount":1750,"commentCount":0,"publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/11\/graphql-golang-todo-list.png","keywords":["Backend","go","graphql"],"articleSection":["Applications","English Content","General"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/","url":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/","name":"Building a GraphQL Example Application with Golang [Tutorial] - inovex GmbH","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#primaryimage"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/11\/graphql-golang-todo-list.png","datePublished":"2020-11-24T14:42:50+00:00","dateModified":"2025-01-22T07:53:25+00:00","description":"This article teaches you the solid basics of implementing a GraphQL interface using golang and the 99designs\/gqlgen library.","breadcrumb":{"@id":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#primaryimage","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/11\/graphql-golang-todo-list.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2020\/11\/graphql-golang-todo-list.png","width":1920,"height":1080,"caption":"graphql golang todo Liste"},{"@type":"BreadcrumbList","@id":"https:\/\/www.inovex.de\/de\/blog\/graphql-application-golang-tutorial\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.inovex.de\/de\/"},{"@type":"ListItem","position":2,"name":"Building a GraphQL Example Application with Golang [Tutorial]"}]},{"@type":"WebSite","@id":"https:\/\/www.inovex.de\/de\/#website","url":"https:\/\/www.inovex.de\/de\/","name":"inovex GmbH","description":"","publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.inovex.de\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/www.inovex.de\/de\/#organization","name":"inovex GmbH","url":"https:\/\/www.inovex.de\/de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","width":1921,"height":1081,"caption":"inovex GmbH"},"image":{"@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/inovexde","https:\/\/x.com\/inovexgmbh","https:\/\/www.instagram.com\/inovexlife\/","https:\/\/www.linkedin.com\/company\/inovex","https:\/\/www.youtube.com\/channel\/UC7r66GT14hROB_RQsQBAQUQ"]},{"@type":"Person","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/ee62ffe3b2170d28a8900df059124599","name":"Gabriel-Mihai Ruiu","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/secure.gravatar.com\/avatar\/4e249b88efe8d06a30e9aa7ede100b0411127e2164c3a424d08cce2f9cb65120?s=96&d=retro&r=gb9f4999c23470399b1bb1d5e7b073040","url":"https:\/\/secure.gravatar.com\/avatar\/4e249b88efe8d06a30e9aa7ede100b0411127e2164c3a424d08cce2f9cb65120?s=96&d=retro&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/4e249b88efe8d06a30e9aa7ede100b0411127e2164c3a424d08cce2f9cb65120?s=96&d=retro&r=g","caption":"Gabriel-Mihai Ruiu"},"url":"https:\/\/www.inovex.de\/de\/blog\/author\/gruiu\/"}]}},"_links":{"self":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/20005","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/users\/201"}],"replies":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/comments?post=20005"}],"version-history":[{"count":2,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/20005\/revisions"}],"predecessor-version":[{"id":60486,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/20005\/revisions\/60486"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media\/20218"}],"wp:attachment":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media?parent=20005"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/tags?post=20005"},{"taxonomy":"service","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/service?post=20005"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/coauthors?post=20005"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}