graphqlapollographql-jsapollo-servergraphql-tools

Enumerating all fields from a GraphQL query


Given a GraphQL schema and resolvers for Apollo Server, and a GraphQL query, is there a way to create a collection of all requested fields (in an Object or a Map) in the resolver function?

For a simple query, it's easy to recreate this collection from the info argument of the resolver.

Given a schema:

type User {
  id: Int!
  username: String!
  roles: [Role!]!
}

type Role {
  id: Int!
  name: String!
  description: String
}

schema {
  query: Query
}

type Query {
  getUser(id: Int!): User!
}

and a resolver:

Query: {
  getUser: (root, args, context, info) => {
    console.log(infoParser(info))

    return db.Users.findOne({ id: args.id })
  }
}

with a simple recursive infoParser function like this:

function infoParser (info) {
  const fields = {}

  info.fieldNodes.forEach(node => {
    parseSelectionSet(node.selectionSet.selections, fields)
  })

  return fields
}

function parseSelectionSet (selections, fields) {
  selections.forEach(selection => {
    const name = selection.name.value

    fields[name] = selection.selectionSet
      ? parseSelectionSet(selection.selectionSet.selections, {})
      : true
  })

  return fields
}

The following query results in this log:

{
  getUser(id: 1) {
    id
    username
    roles {
      name
    }
  }
}

=> { id: true, username: true, roles: { name: true } }

Things get pretty ugly pretty soon, for example when you use fragments in the query:

fragment UserInfo on User {
  id
  username
  roles {
    name
  }
}

{
  getUser(id: 1) {
    ...UserInfo
    username
    roles {
      description
    }
  }
}

GraphQL engine correctly ignores duplicates, (deeply) merges etc. queried fields on execution, but it is not reflected in the info argument. When you add unions and inline fragments it just gets hairier.

Is there a way to construct a collection of all fields requested in a query, taking in account advanced querying capabilities of GraphQL?

Info about the info argument can be found on the Apollo docs site and in the graphql-js Github repo.


Solution

  • I know it has been a while but in case anyone ends up here, there is an npm package called graphql-list-fields by Jake Pusareti that does this. It handles fragments and skip and include directives. you can also check the code here.