# GraphQL Basics: Demystifying the `info` Argument in GraphQL Resolvers URL: https://www.prisma.io/blog/graphql-server-basics-demystifying-the-info-argument-in-graphql-resolvers-6f26249f613a Published: 2018-02-06 Authors: Nikolas Burk Description: Learn what the `info` argument in GraphQL resolvers contains and how it helps with selection sets, execution context, and server internals. Structure and Implementation of GraphQL Servers (Part III) If you’ve written a GraphQL server before, chances are you’ve already come across the info object that gets passed into your resolvers. Luckily in most cases, you don’t really need to understand what it actually does and what its role is during query resolution. However, there are a number of edge cases where the info object is the cause of a lot of confusion and misunderstandings. The goal of this article is to take a look under the covers of the info object and shed light on its role in the GraphQL [execution](http://graphql.org/learn/execution/) process. > This article assumes you’re already familiar with the basics of how GraphQL queries and mutations are resolved. If you feel a bit shaky in this regard, you should definitely check out the previous articles of this series: > Part I: [The GraphQL Schema](https://www.prisma.io/blog/graphql-server-basics-the-schema-ac5e2950214e) (required) > Part II: [The Network Layer](https://www.prisma.io/blog/graphql-server-basics-the-network-layer-51d97d21861) (optional) ## Structure of the `info` object [#structure-of-the-info-object] ### Recap: The signature of GraphQL resolvers [#recap-the-signature-of-graphql-resolvers] A quick recap, when building a GraphQL server with [GraphQL.js](https://github.com/graphql/graphql-js), you have two major tasks: * Define your [GraphQL schema](https://www.prisma.io/blog/graphql-server-basics-the-schema-ac5e2950214e) (either in SDL or as a plain JS object) * For each field in your schema, implement a *resolver* function that knows how to return the value for that field A resolver function takes four arguments (in that order): 1. `parent`: The result of the previous resolver call ([more info](https://www.prisma.io/blog/graphql-server-basics-the-schema-ac5e2950214e)). 2. `args`: The arguments of the resolver’s field. 3. `context`: A custom object each resolver can read from/write to. 4. `info`: *That’s what we’ll discuss in this article.* Here is an overview of the execution process of a simple GraphQL query and the invocations of the belonging resolvers. Because the resolution of the *2nd resolver level* is trivial, there is no need to actually implement these resolvers — their return values are automatically inferred by GraphQL.js: ![](https://www.prisma.io/blog/graphql-server-basics-demystifying-the-info-argument-in-graphql-resolvers-6f26249f613a/imgs/1-2UStyS7v3NIZKl2cLsBtYA.png) *Overview of the `parent` and `args` argument in the GraphQL resolver chain* ### `info` contains the query AST and more execution information [#info-contains-the-query-ast-and-more-execution-information] Those curios about the structure and the role of the info object are left in the dark. Neither the official [spec](https://spec.graphql.org/October2016), nor the [documentation](http://graphql.org/graphql-js/) are mentioning it at all. There used to be a GitHub [issue](https://github.com/graphql/graphql-js/issues/799) requesting better documentation for it, but that was closed without notable action. So, there’s no other way than digging into the code. On a very high-level, it can be stated that the info object contains the AST of the incoming GraphQL query. Thanks to that, the resolvers know which fields they need to return. > To learn more about what query ASTs look like, be sure to check out [Christian Joudrey](https://twitter.com/cjoudrey)’s fantastic article [Life of a GraphQL Query — Lexing/Parsing](https://medium.com/@cjoudrey/life-of-a-graphql-query-lexing-parsing-ca7c5045fad8) as well as [Eric Baer](https://twitter.com/ebaerbaerbaer)’s brilliant talk [GraphQL Under the Hood](https://www.youtube.com/watch?v=fo6X91t3O2I). To understand the structure of `info`, let’s take a look at [its Flow type definition](https://github.com/graphql/graphql-js/blob/bacd412770f4c21f40d403d605420e9f4fd9ed2f/src/type/definition.js#L584-L595): ```js /* @flow */ export type GraphQLResolveInfo = { fieldName: string, fieldNodes: Array, returnType: GraphQLOutputType, parentType: GraphQLCompositeType, path: ResponsePath, schema: GraphQLSchema, fragments: { [fragmentName: string]: FragmentDefinitionNode }, rootValue: mixed, operation: OperationDefinitionNode, variableValues: { [variableName: string]: mixed }, } ``` Here’s an overview and quick explanation for each of these keys: * `fieldName`: As mentioned before, each field in your GraphQL schema needs to be backed by a resolver. The `fieldName` contains the name for the field that belongs to the current resolver. * `fieldNodes`: An array where each object represents a field in the remaining *selection set*. * `returnType`: The GraphQL type of the corresponding field. * `parentType`: The GraphQL type to which this field belongs. * `path`: Keeps track of the fields that were traversed until the current field (i.e. resolver) was reached. * `schema`: The [`GraphQLSchema`](http://graphql.org/graphql-js/type/#graphqlschema) instance representing your *executable* schema. * `fragments`: A map of [fragments](http://graphql.org/learn/queries/#fragments) that were part of the query document. * `rootValue`: The [`rootValue`](https://github.com/graphql/graphql-js/blob/bacd412770f4c21f40d403d605420e9f4fd9ed2f/src/graphql.js#L46) argument that was passed to the execution. * `operation`: The AST of the *entire* query. * `variableValues`: A map of any variables that were provided along with the query corresponds to the [variableValues](https://github.com/graphql/graphql-js/blob/bacd412770f4c21f40d403d605420e9f4fd9ed2f/src/graphql.js#L48) argument. Don’t worry if that still seems abstract, we’ll see examples for all of these soon. ### Field-specific vs Global [#field-specific-vs-global] There is one interesting observation to be made regarding the keys above. A key on the `info` object is either *field-specific* or *global*. *Field-specific* simply means that the value for that key depends on the field (and its backing resolver) to which the `info` object is passed. Obvious examples are `fieldName`, `rootType` and `parentType`. Consider the `author` field of the following GraphQL type: ```graphql type Query { author: User! feed: [Post!]! } ``` The `fieldName` for that field is just `author`, the `returnType` is `User!` and the `parentType` is `Query`. Now, for `feed` these values will of course be different: the `fieldName` is `feed`, `returnType` is `[Post!]!` and the `parentType` is also `Query`. So, the values for these three keys are field-specific. Further field-specific keys are: `fieldNodes` and `path`. Effectively, the first five keys of the Flow definition above are field-specific. *Global*, on the other hand, means the values for these keys won’t change — no matter which resolver we’re talking about. `schema`, `fragments`, `rootValue`, `operation` and `variableValues` will always carry the same values for all resolvers. ## A simple example [#a-simple-example] Let’s now go ahead and see an example for the contents of the `info` object. To set the stage, here is the [schema definition](https://www.prisma.io/blog/graphql-server-basics-the-schema-ac5e2950214e) we’ll use for this example: ```graphql type Query { author(id: ID!): User! feed: [Post!]! } type User { id: ID! username: String! posts: [Post!]! } type Post { id: ID! title: String! author: User! } ``` Assume the resolvers for that schema are implemented as follows: ```js const resolvers = { Query: { author: (root, { id }, context, info) => { console.log(`Query.author - info: `, JSON.stringify(info)) return users.find(u => u.id === id) }, feed: (root, args, context, info) => { console.log(`Query.feed - info: `, JSON.stringify(info)) return posts }, }, Post: { title: (root, args, context, info) => { console.log(`Post.title - info: `, JSON.stringify(info)) return root.title }, }, } ``` > Note that the `Post.title` resolver is not actually required, we still include it here to see what the `info` object looks like when the resolver gets called. Now consider the following query: ```graphql query AuthorWithPosts { author(id: "user-1") { username posts { id title } } } ``` For the purpose of brevity, we’ll only discuss the resolver for the `Query.author` field, not the one for `Post.title` (which is still invoked when the above query is executed). > If you want to play around with this example, we prepared a [repository](https://github.com/nikolasburk/info-playground) with a running version of the above schema so you have something to experiment with! Next, let’s take a look at each of the keys inside the `info` object and see what they look like when the `Query.author` resolver is invoked (you can find the entire logging output for the `info` object [here](https://github.com/nikolasburk/info-playground/blob/master/info/Query.author-info.json)). ### `fieldName` [#fieldname] The `fieldName` is simply `author`. ### `fieldNodes` [#fieldnodes] Remember that `fieldNodes` is field-specific. It effectively contains an *excerpt* of the query AST. This excerpt starts at the current field (i.e. `author`) rather than at the *root* of the query. (The entire query AST which starts at the root is stored in `operation`, see below). ```json { "fieldNodes": [ { "kind": "Field", "name": { "kind": "Name", "value": "author", "loc": { "start": 27, "end": 33 } }, "arguments": [ { "kind": "Argument", "name": { "kind": "Name", "value": "id", "loc": { "start": 34, "end": 36 } }, "value": { "kind": "StringValue", "value": "user-1", "block": false, "loc": { "start": 38, "end": 46 } }, "loc": { "start": 34, "end": 46 } } ], "directives": [], "selectionSet": { "kind": "SelectionSet", "selections": [ { "kind": "Field", "name": { "kind": "Name", "value": "username", "loc": { "start": 54, "end": 62 } }, "arguments": [], "directives": [], "loc": { "start": 54, "end": 62 } }, { "kind": "Field", "name": { "kind": "Name", "value": "posts", "loc": { "start": 67, "end": 72 } }, "arguments": [], "directives": [], "selectionSet": { "kind": "SelectionSet", "selections": [ { "kind": "Field", "name": { "kind": "Name", "value": "id", "loc": { "start": 81, "end": 83 } }, "arguments": [], "directives": [], "loc": { "start": 81, "end": 83 } }, { "kind": "Field", "name": { "kind": "Name", "value": "title", "loc": { "start": 90, "end": 95 } }, "arguments": [], "directives": [], "loc": { "start": 90, "end": 95 } } ], "loc": { "start": 73, "end": 101 } }, "loc": { "start": 67, "end": 101 } } ], "loc": { "start": 48, "end": 105 } }, "loc": { "start": 27, "end": 105 } } ] } ``` ### `returnType` & `parentType` [#returntype--parenttype] As seen before, `returnType` and `parentType` are fairly trivial: ```json { "returnType": "User!", "parentType": "Query" } ``` ### `path` [#path] The `path` tracks the fields which have been traversed until the current one. For `Query.author`, it simply looks like`"path": { "key": "author" }`. ```json { "path": { "key": "author" } } ``` For comparison, in the `Post.title` resolver, the `path` looks as follows: ```json { "path": { "prev": { "prev": { "prev": { "key": "author" }, "key": "posts" }, "key": 0 }, "key": "title" } } ``` > The remaining five fields fall into the “global” category and therefore will be identical for the `Post.title` resolver. ### `schema` [#schema] The `schema` is a reference to the executable schema. ### `fragments` [#fragments] `fragments` contains fragment definitions, since the query document doesn’t have any of those, it’s just an empty map: `{}`. ### `rootValue` [#rootvalue] As mentioned before, the value for the `rootValue` key corresponds to the [`rootValue`](https://github.com/graphql/graphql-js/blob/bacd412770f4c21f40d403d605420e9f4fd9ed2f/src/graphql.js#L46) argument that’s passed to the graphql execution function in the first place. In the case of the example, it’s just `null`. ### `operation` [#operation] `operation` contains the full [query AST](https://medium.com/@cjoudrey/life-of-a-graphql-query-lexing-parsing-ca7c5045fad8) of the incoming query. Recall that among other information, this contains the same values we saw for `fieldNodes` above: ```json { "operation": { "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "AuthorWithPosts" }, "selectionSet": { "kind": "SelectionSet", "selections": [ { "kind": "Field", "name": { "kind": "Name", "value": "author" }, "arguments": [ { "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "StringValue", "value": "user-1" } } ], "selectionSet": { "kind": "SelectionSet", "selections": [ { "kind": "Field", "name": { "kind": "Name", "value": "username" } }, { "kind": "Field", "name": { "kind": "Name", "value": "posts" }, "selectionSet": { "kind": "SelectionSet", "selections": [ { "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "title" } } ] } } ] } } ] } } } ``` ### `variableValues` [#variablevalues] This key represents any variables that have been passed for the query. As there are no variables in our example, the value for this again is just an empty map: `{}`. If the query was written with variables: ```graphql query AuthorWithPosts($userId: ID!) { author(id: $userId) { username posts { id title } } } ``` The `variableValues` key would simply have the following value: ```json { "variableValues": { "userId": "user-1" } } ``` ## The role of `info` when using GraphQL bindings [#the-role-of-info-when-using-graphql-bindings] As mentioned in the beginning of the article, in most scenarios you don’t need to worry at all about the `info` object. It just happens to be part of your resolver signatures, but you’re not actually using it for anything. So, when does it become relevant? ### Passing `info` to binding functions [#passing-info-to-binding-functions] If you’ve worked with [GraphQL bindings](https://github.com/dotansimha/graphql-binding) before, you’ve seen the `info` object as part of the generated binding functions. Consider the following schema: ```graphql type Query { users(): [User]! user(id: ID!): User } type Mutation { createUser(username: String!): User! deleteUser(id: ID!!): User } type User { id: ID! username: String! } ``` Using `graphql-binding`, you can now send the available queries and mutations by invoking dedicated *binding functions* rather than sending over raw queries and mutations. For example, consider the following raw query, retrieving a specific `User`: ```graphql query { user(id: "user-100") { id username } } ``` Achieving the same with a binding function would look as follows: ```js binding.query.user({ id: 'user-100' }, null, '{ id username }') ``` With the invocation of the `user` function on the binding instance and by passing the corresponding arguments, we convey exactly the same information as with the raw GraphQL query above. A binding function from `graphql-binding` takes three arguments: 1. `args`: Contains the arguments for the field (e.g. the `username` for the `createUser` mutation above). 2. `context`: The `context` object that’s passed down the resolver chain. 3. `info`: The `info` object. Note that rather than an instance of `GraphQLResolveInfo` (which is the type of info) you can also pass a string that simply defines the selection set. ### Mapping application schema to database schema with Prisma [#mapping-application-schema-to-database-schema-with-prisma] Another common use case where the info object can cause confusion is the implementation of a GraphQL server based on [Prisma](https://www.prisma.io/) and [prisma-binding](https://github.com/prismagraphql/prisma-binding). In that context, the idea is to have two GraphQL layers: * the ***database layer*** is automatically generated by Prisma and provides a generic and powerful CRUD API * the\* **application layer\*** defines the GraphQL API that’s exposed to client applications and tailored to your application’s needs As a backend developer, you’re responsible to define the *application schema* for the application layer and implement its resolvers. Thanks to `prisma-binding`, the implementation of the resolvers merely is a process of [*delegating*](https://www.prisma.io/blog/graphql-schema-stitching-explained-schema-delegation-4c6caf468405) incoming queries to the underlying database API without major overhead. Let’s consider a simple example — say you’re starting out with the following data model for your Prisma database service: ```graphql type Post { id: ID! @unique title: String! author: User! } type User { id: ID! @uniqe name: String! posts: [Post!]! } ``` The database schema that Prisma generates based on this data model looks similar to this: ```graphql type Query { posts( where: PostWhereInput orderBy: PostOrderByInput skip: Int after: String before: String first: Int last: Int ): [Post]! postsConnection( where: PostWhereInput orderBy: PostOrderByInput skip: Int after: String before: String first: Int last: Int ): PostConnection! post(where: PostWhereUniqueInput!): Post users( where: UserWhereInput orderBy: UserOrderByInput skip: Int after: String before: String first: Int last: Int ): [User]! usersConnection( where: UserWhereInput orderBy: UserOrderByInput skip: Int after: String before: String first: Int last: Int ): UserConnection! user(where: UserWhereUniqueInput!): User } type Mutation { createPost(data: PostCreateInput!): Post! updatePost(data: PostUpdateInput!, where: PostWhereUniqueInput!): Post deletePost(where: PostWhereUniqueInput!): Post createUser(data: UserCreateInput!): User! updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User deleteUser(where: UserWhereUniqueInput!): User } ``` Now, assume you want to build an application schema looking similar to this: ```graphql type Query { feed(authorId: ID): Feed! } type Feed { posts: [Post!]! count: Int! } ``` The `feed` query not only returns a list of `Post` elements, but is also able to return the `count` of the list. Note that it optionally takes an `authorId` which filters the feed to only return `Post` elements written by a specific `User`. A first intuition to implement this application schema might look as follows. **IMPLEMENTATION 1: This implementation looks correct but has a subtle flaw:** ```js const resolvers = { Query: { async feed(parent, { authorId }, ctx, info) { // build filter const authorFilter = authorId ? { author: { id: authorId } } : {} // retrieve (potentially filtered) posts const posts = await ctx.db.query.posts({ where: authorFilter }) // retrieve (potentially filtered) element count const postsConnection = await ctx.db.query.postsConnection({ where: authorFilter }, `{ aggregate { count } }`) return { count: postsConnection.aggregate.count, posts: posts, } }, }, } ``` This implementation seems reasonable enough. Inside the `feed` resolver, we’re constructing the `authorFilter` based on the potentially incoming `authorId`. The `authorFilter` is then used to execute the `posts` query and retrieve the `Post` elements, as well as the `postsConnection` query which gives access to the `count` of the list. > It would also be possible to retrieve the actual *Post* elements using just the *postsConnection* query. To keep things simple, we’re still using the *posts* query for that and leave the other approach as an exercise to the attentive reader. In fact, when starting your GraphQL server with this implementation, things will seem good at first sight. You’ll notice that simple queries are served properly, for example the following query will succeed: ```graphql query { feed(authorId: "cjdbbsepg0wp70144svbwqmtt") { count posts { id title } } } ``` It isn’t until you’re trying to retrieve the `author` of the `Post` elements when you’re running into an issue: ```graphql query { feed(authorId: "cjdbbsepg0wp70144svbwqmtt") { count posts { id title author { id name } } } } ``` All right! So, for some reason the implementation doesn’t return the `author` and that triggers an error *"Cannot return null for non-nullable Post.author."* because the `Post.author` field is marked as required in the *application schema*. Let’s take a look again at the relevant part of the implementation: ```js // retrieve (potentially filtered) posts const posts = await ctx.db.query.posts({ where: authorFilter }) ``` Here is where we retrieve the Post elements. However, we’re not passing a *selection set* to the posts binding function. If no second argument is passed to a Prisma binding function, the default behaviour is to query all *scalar* fields for that type. This indeed explains the behaviour. The call to `ctx.db.query.posts` returns the correct set of `Post` elements, but only their `id` and `title` values — no relational data about the `author`s. So, how can we fix that? What’s needed obviously is a way to tell the `posts` binding function which fields it needs to return. But where does that information reside in the context of the `feed` resolver? Can you guess that? *Correct:* Inside the `info` object! Because the second argument for a Prisma binding function can either be a string *or* an `info` object, let’s just pass the `info` object which gets passed into the `feed` resolver on to the `posts` binding function. **This query fails with IMPLEMENTATION 2: “Field ‘posts’ of type ‘Post’ must have a sub selection.”** ```js const resolvers = { Query: { async feed(parent, { authorId }, ctx, info) { // build filter const authorFilter = authorId ? { author: { id: authorId } } : {} // retrieve (potentially filtered) posts const posts = await ctx.db.query.posts({ where: authorFilter }, info) // pass `info` // retrieve (potentially filtered) element count const postsConnection = await ctx.db.query.postsConnection({ where: authorFilter }, `{ aggregate { count } }`) return { count: postsConnection.aggregate.count, posts: posts, } }, }, } ``` With this implementation however, *no* request will be properly served. As an example, consider the following query: ```js query { feed { count posts { title } } } ``` The error message *"Field ‘posts’ of type ‘Post’ must have a sub selection."* is produced by *line 8* of the above implementation. So, what is happening here? The reason why this fails because the *field-specific* keys in the `info` object don’t match up with the `posts` query. Printing the `info` object inside the `feed` resolver sheds more light on the situation. Let’s consider only the field-specific information in `fieldNodes`: ```json { "fieldNodes": [ { "kind": "Field", "name": { "kind": "Name", "value": "feed" }, "arguments": [], "directives": [], "selectionSet": { "kind": "SelectionSet", "selections": [ { "kind": "Field", "name": { "kind": "Name", "value": "count" }, "arguments": [], "directives": [] }, { "kind": "Field", "name": { "kind": "Name", "value": "posts" }, "arguments": [], "directives": [], "selectionSet": { "kind": "SelectionSet", "selections": [ { "kind": "Field", "name": { "kind": "Name", "value": "title" }, "arguments": [], "directives": [] } ] } } ] } } ] } ``` This JSON object can be represented as as string selection set as well: ```graphql { feed { count posts { title } } } ``` Now it all makes sense! We’re sending the above selection set to the [`posts`](https://gist.github.com/gc-codesnippets/b7f3d713a343262d7646724c5a5be2d8#file-prisma-graphql-L197) query of the Prisma database schema which of course is not aware of the `feed` and `count` fields. Admittedly, the error message that’s produced is not super helpful but at least we understand what’s going on now. So, what’s the solution to this problem? One way to approach this issue would be to *manually* parse out the correct part of the selection set of `fieldNodes` and pass it to the `posts` binding function (e.g. as a string). However, there is a much more elegant solution to the problem, and that is to implement dedicated resolver for the `Feed` type from the application schema. Here is what the proper implementation looks like. **IMPLEMENTATION 3: This implementation fixes the above issues** ```js const resolvers = { Query: { async feed(parent, { authorId }, ctx, info) { // build filter const authorFilter = authorId ? { author: { id: authorId } } : {} // retrieve (potentially filtered) posts const posts = await ctx.db.query.posts({ where: authorFilter }, `{ id }`) // second argument can also be omitted // retrieve (potentially filtered) element count const postsConnection = await ctx.db.query.postsConnection({ where: authorFilter }, `{ aggregate { count } }`) return { count: postsConnection.aggregate.count, postIds: posts.map(post => post.id), // only pass the `postIds` down to the `Feed.posts` resolver } }, }, Feed: { posts({ postIds }, args, ctx, info) { const postIdsFilter = { id_in: postIds } return ctx.db.query.posts({ where: postIdsFilter }, info) }, }, } ``` This implementation fixes all the issues that were discussed above. There are few things to note: * In *line 8*, we’re now passing a string selection set ( `{ id }`) as a second argument. This is just for efficiency since otherwise all the scalar values would be fetched (which wouldn’t make a huge difference in our example) where we only need the IDs. * Rather than returning `posts` from the `Query.feed` resolver, we’re returning `postIds` which is just an array of IDs (represented as strings). * In the `Feed.posts` resolver, we can now access the `postIds` which were returned by the *parent* resolver. This time, we can make use the incoming `info` object and simply pass it on to the `posts` binding function. > If you want to play around with this example, you can check out [this](https://github.com/nikolasburk/info-prisma-example) repository which contains a running version of the above example. Feel free to try out the different implementations mentioned in this article and observe the behaviour yourself! ## Summary [#summary] In this article, you got deep insights into the `info` object which is used when implementing a GraphQL API based on [GraphQL.js](https://github.com/graphql/graphql-js). The `info` object is not officially documented — to learn more about it you need to dig into the code. In this tutorial, we started by outlining its internal structure and understanding its role in GraphQL resolver functions. We then covered a few edge cases and potential traps where a deeper understanding of `info` is required. All the code that was shown in this article can be found in corresponding GitHub repositories so you can experiment and observe the behaviour of the info object yourself.