Graphql: question: nested fields example

Created on 13 May 2016  路  10Comments  路  Source: graphql-go/graphql

Is there an example of how to configure nested fields similar to the below (JS) ? I tried converting this js example to go but it didn't end well. Thanks

query:

{
  posts {
    title,
    author {
      name
    }
  }
}

code:
https://gist.github.com/arunoda/c29128be2c5e979475ec

question

Most helpful comment

Hi @kmulvey

Maybe you could share how far you've gotten into the go version, we could help you better that way :)

In the meantime, this is how I would do:

  1. First I'd define the schema using GraphQL Schema Language _(shameless plug: cheat sheet here )_
type RootQuery {
    post: Post
}
type Post {
    _id: String!
    title: String
    content: String
    author: Author
}
type Author {
    _id: String!
    name: String
}
  1. Next, here's is a rough equivalent of the JS version that you posted.
    Note that I've left out the business / app logic to retrieve/find posts/author etc.

Hope this helps you move into the right direction, let me know if you need further help.

Cheers!

package main

import (
    "github.com/graphql-go/graphql"
    "fmt"
)
// Define GraphQL Schema
var AuthorObject = graphql.NewObject(graphql.ObjectConfig{
    Name: "Author",
    Description: "This respresents an Author",
    Fields: graphql.Fields{
        "_id": &graphql.Field{
            Type: graphql.NewNonNull(graphql.String),
        },
        "name": &graphql.Field{
            Type: graphql.String,
        },
    },
})
var PostObject = graphql.NewObject(graphql.ObjectConfig{
    Name: "Post",
    Description: "This respresents an Post",
    Fields: graphql.Fields{
        "_id": &graphql.Field{
            Type: graphql.NewNonNull(graphql.String),
        },
        "title": &graphql.Field{
            Type: graphql.NewNonNull(graphql.String),
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                // get title from source
                title, _ := p.Source["title"].(string)

                if title == "" {
                    // return error
                    return nil, fmt.Errorf("Does not exists")
                }
                return title, nil
            },
        },
        "content": &graphql.Field{
            Type: graphql.String,
        },
        "author": &graphql.Field{
            Type: AuthorObject,
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                // get title from source
                title, _ := p.Source["title"].(string)

                // add business logic to retrieve find for given post title
                return author, nil
            },
        },
    },
})
var RootQuery = graphql.NewObject(graphql.ObjectConfig{
    Name: "BlogSchema",
    Description: "Root of the Blog Schema",
    Fields: graphql.Fields{
        "posts": &graphql.Field{
            Type: graphql.NewList(PostObject),
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                // add business logic to retrieve list of Posts
                return PostsList
            },
        },
        "echo": &graphql.Field{
            Type: graphql.String,
            Description: "Echo what you enter",
            Args: graphql.FieldConfigArgument{
                "message": &graphql.ArgumentConfig{
                    Type: graphql.String,
                },
            },
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                // get arg value
                message, _ := p.Args["message"].(string)
                return message, nil
            },
        },
    },
})

All 10 comments

Hi @kmulvey

Maybe you could share how far you've gotten into the go version, we could help you better that way :)

In the meantime, this is how I would do:

  1. First I'd define the schema using GraphQL Schema Language _(shameless plug: cheat sheet here )_
type RootQuery {
    post: Post
}
type Post {
    _id: String!
    title: String
    content: String
    author: Author
}
type Author {
    _id: String!
    name: String
}
  1. Next, here's is a rough equivalent of the JS version that you posted.
    Note that I've left out the business / app logic to retrieve/find posts/author etc.

Hope this helps you move into the right direction, let me know if you need further help.

Cheers!

package main

import (
    "github.com/graphql-go/graphql"
    "fmt"
)
// Define GraphQL Schema
var AuthorObject = graphql.NewObject(graphql.ObjectConfig{
    Name: "Author",
    Description: "This respresents an Author",
    Fields: graphql.Fields{
        "_id": &graphql.Field{
            Type: graphql.NewNonNull(graphql.String),
        },
        "name": &graphql.Field{
            Type: graphql.String,
        },
    },
})
var PostObject = graphql.NewObject(graphql.ObjectConfig{
    Name: "Post",
    Description: "This respresents an Post",
    Fields: graphql.Fields{
        "_id": &graphql.Field{
            Type: graphql.NewNonNull(graphql.String),
        },
        "title": &graphql.Field{
            Type: graphql.NewNonNull(graphql.String),
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                // get title from source
                title, _ := p.Source["title"].(string)

                if title == "" {
                    // return error
                    return nil, fmt.Errorf("Does not exists")
                }
                return title, nil
            },
        },
        "content": &graphql.Field{
            Type: graphql.String,
        },
        "author": &graphql.Field{
            Type: AuthorObject,
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                // get title from source
                title, _ := p.Source["title"].(string)

                // add business logic to retrieve find for given post title
                return author, nil
            },
        },
    },
})
var RootQuery = graphql.NewObject(graphql.ObjectConfig{
    Name: "BlogSchema",
    Description: "Root of the Blog Schema",
    Fields: graphql.Fields{
        "posts": &graphql.Field{
            Type: graphql.NewList(PostObject),
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                // add business logic to retrieve list of Posts
                return PostsList
            },
        },
        "echo": &graphql.Field{
            Type: graphql.String,
            Description: "Echo what you enter",
            Args: graphql.FieldConfigArgument{
                "message": &graphql.ArgumentConfig{
                    Type: graphql.String,
                },
            },
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                // get arg value
                message, _ := p.Args["message"].(string)
                return message, nil
            },
        },
    },
})

thanks @sogko for the quick reply. I was able to advance your example a bit and get the correct structure out but now the values are coming out null. I have a feeling I made a silly mistake somewhere but can't seem to spot it. Also authorObject (commented out) won't compile if i leave the "posts" section in. Thanks for your help.

https://gist.github.com/kmulvey/4843061d16492a24ce2663e497dda528

{
  "data": {
    "posts": [
      {
        "author": {
          "name": null
        },
        "title": null
      },
      {
        "author": {
          "name": null
        },
        "title": null
      },
       etc ...
    ]
  }
}

hi @kmulvey

Actually you almost had it.
Since you are returning Golang structs in Resolve() functions, you need to let graphql-go know how to map the struct fields to your GraphQL fields.
Currently we use json tags for this.

(This is shown in the examples, but unfortunately we have not done a good job with the documentation to highlight this)

Simply update your struct implementations with the json tags, mapping it to the field names defined in your schema. (Note the capitalisation)

// User respresents an author
type User struct {
    ID    int    `json:"_id"`
    Age   int    `json:"age"`
    Name  string `json:"name"`
    Posts []Post `json:"posts"`
}

// Post is a blog post
type Post struct {
    ID     int    `json:"_id"`
    UserID int    `gorm:"index"` // json-tag not needed since you are not using this field currently
    Title  string `gorm:"type:varchar(100);unique_index" json:"title"`
}
{
  "data": {
    "posts": [
      {
        "author": {
          "name": "userOne"
        },
        "title": "post one"
      },
      {
        "author": {
          "name": "userOne"
        },
        "title": "post two"
      },
      {
        "author": {
          "name": "userTwo"
        },
        "title": "post three"
      },
      {
        "author": {
          "name": "userTwo"
        },
        "title": "post four"
      }
    ]
  }
}

If I find the time, I can do a complete implementation of the example gist you gave earlier, to compare notes.

Cheers!

What if I need to perform both
{posts {title,author {name}}}
and
{user {name,posts {title}}}
queries?

@artshpakov

query posts {
  posts {
    title
    author {
      name
    }
  }
  user {
    name
    posts {
      title
    }
  }
}

I'm not sure your question is related to the issue you posted in. Nor am I sure exactly what you're asking.

@bbuck
Sorry if I misunderstood some graphql concepts in general, but I was referring to the golang code snippet above. It shows how to implement your Post and User types in case you want to query posts with authors embedded. So you've got to have your AuthorObject to be defined when you define PostObject. What I'm asking about is the case when you need both posts with nesting author
posts{title text author{name}} and authors with nested posts author(id:1){name posts{title text}}. You cannot have a "posts" field on AuthorObject because you have no PostObject defined yet.

Like, in Ruby graphql gem you can have that with

PostType = GraphQL::ObjectType.define do
  field :title, !types.String
  field :text, !types.String

  field :author, types[UserType] do
    resolve -> (obj, args, ctx) { User.find(obj.user_id) }
  end
end

UserType = GraphQL::ObjectType.define do
  field :name, !types.String

  field :posts, types[PostType] do
    resolve -> (obj, args, ctx) { obj.posts }
  end
end

QueryType = GraphQL::ObjectType.define do
  field :posts, types[PostType] do
    resolve ->(obj, args, ctx) { Post.all }
  end

  field :user do
    type UserType
    argument :id, !types.ID
    resolve -> (obj, args, ctx) { User.find(args["id"]) }
  end
end

What I'm asking is if there's any way to do the same with graphql-go.

@artshpakov Ah, okay. So basically, in the case of types that have to reference other types and the requirement for the type to be non-null in GraphQL you'd delay field definition.

Define your Post type without Authors, and define your Authors type without posts, then once the two exist without reference each other use Object.AddFieldConfig to tie them together (add a posts to Author and add a author to Post).

@bbuck I see. That will quickly become clumsy though -- at least when your APIs start to grow and you decide to have your type definitions in separate files.

We use it in production and don't see any issues with it. I'm not sure how it can become clumsy.

Sorry to revive an old thread - I can open a new issue but it's related to this comment: https://github.com/graphql-go/graphql/issues/129#issuecomment-219200124.

In the example, the RootQuery has a posts field that has a Resolve that fetches all the posts and returns them.

My understanding is that then the PostObject resolvers start trying to figure out the _id, title, content and author. The author resolver uses p.Source to determine which author to look up and return.

I have a local example of something very similar, but for the author resolve function, p.Source is []Posts where I expected it to be a single Post and just have graphql-go call my resolver once for each Post the initial resolver received.

Is this a graphql thing?

How am I supposed to use p.Source to determine a particular author if I'm getting an array back?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

zbintliff picture zbintliff  路  3Comments

sogko picture sogko  路  6Comments

FrontMage picture FrontMage  路  5Comments

sanae10001 picture sanae10001  路  5Comments

Fruchtgummi picture Fruchtgummi  路  4Comments