2

Given I have a schema that has types: User, Comment, Post, Image; Is it possible to determine the GraphQL types being used in a query, given the query and schema? e.g. if a client had a query

{
    user(userName: "username") {
        email
        comments
    }
}

In this case, the query has types User and Comment. Is it possible to determine the programmatically using either the graphql-js or graphql packages for node.js?

user277931
  • 93
  • 5

3 Answers3

7

For anyone else who runs into this, I found the answer in visit and TypeInfo Here's a function that takes the GraphQL query (document) and a schema and returns which data types from the schema are being used.

import { visit } from 'graphql/language/visitor'
import { parse } from 'graphql/language'
import { TypeInfo, visitWithTypeInfo } from 'graphql'
import { schema as _schema } from '../src/schema/schema'

const getQueryTypes = (schema, query) => {
    const typeInfo = new TypeInfo(schema)
    var typesMap = {}
    var visitor = {
        enter(node) {
            typeInfo.enter(node)
            typesMap[typeInfo.getType()] = true
        },
        leave(node) {
            typesMap[typeInfo.getType()] = true
            typeInfo.leave(node)
        }
    }
    visit(parse(query), visitWithTypeInfo(typeInfo, visitor))
    return Object.keys(typesMap)
}

const _query = `
    query {
        ...
    } 
`

console.log(getQueryTypes(_schema, _query))
user277931
  • 93
  • 5
  • 2
    visitWithTypeInfo is the way to go. but no need to perform the enter(node) and leave(node) manually as visitWithTypeInfo will create a visitor that does that automatically behind the scene. – Morio Feb 06 '22 at 05:22
0

You have to create types using GraphQLObjectType, for example:

export default new GraphQLObjectType(
  ({
    name: 'User',
    description: 'Represents a User',
    fields: () => ({
      _id: {
        type: GraphQLNonNull(GraphQLString),
        resolve: user => user._id,
      },
      name: {
        type: GraphQLNonNull(GraphQLString),
        description: 'Name of the user',
        resolve: user => user.name,
      },
      createdAt: {
        type: GraphQLString,
        description: '',
        resolve: user => user.createdAt.toISOString(),
      },
      updatedAt: {
        type: GraphQLString,
        description: '',
        resolve: user => user.updatedAt.toISOString(),
      },
    }),
  }: GraphQLObjectTypeConfig<User, GraphQLContext>),
);

Then you can use this UserType on another type, by declaring type: GraphQLNonNull(UserType) for example.

https://graphql.org/graphql-js/constructing-types/

Hope it helps :)

denyzprahy
  • 820
  • 11
  • 24
  • Thanks for the quick reply Denis!, I understand how to create GraphQL types. What I'm wondering is more like if I have a query and a schema, is there a function/method that could tell me what types from the Schema are being used in the Query. I've separated my GraphiQL UI from my GraphQL server. They are running as separate applications, and I want the GraphiQL UI to be able to determine which types are being used in a query, before it sends the query to the GraphQL server. – user277931 Nov 02 '18 at 18:27
  • 1
    @user277931 GraphiQL performs an introspection query against the provided GraphQL endpoint and regenerates the server's schema based on the result. This schema is used to provide the various features you see when using GraphiQL, like linting, auto-completion and the docs panel. So wrt GraphiQL, it already knows the types for any given field or argument. All that is handled for you under the hood. What exactly are you trying to accomplish through GraphiQL? – Daniel Rearden Nov 02 '18 at 20:49
  • @DanielRearden our graphql server needs to handle authorization for the different resolvers. e.g. some clients can see Users and Posts and others can only Users. To do this, our clients must pass in a HTTP header called 'Data-Types' to specify what types they are trying to send in. It looks something like 'Data-Types: User,Post'. To make this easier on developers, we want our Graphiql instance to automatically determine which types to include in the 'Data-Types' header. Does that make sense? – user277931 Nov 05 '18 at 15:15
  • Interesting use case. For what it's worth, you might also be able to create a "visitor" (you can look at the validation rules for examples https://github.com/graphql/graphql-js/tree/master/src/validation/rules) and then call `visit` using the document's AST and the visitor object. – Daniel Rearden Nov 05 '18 at 15:34
0

Given a valid document string representing some GraphQL operation, you can parse the string into an AST.

import { parse, validate } from 'graphql' 

const document = parse(someDocumentString)
// if you want to validate your document to verify it matches your schema
const errors = validate(schema, document)

AFAIK, there's no utility function for getting an array of the types in a document, if that's what you're asking for, but you can just traverse the AST and gather whatever information from it. As an example, here's how GraphiQL does just that to generate a map of variables to their corresponding types:

import { typeFromAST } from 'graphql'

export function collectVariables(schema, documentAST) {
  const variableToType = Object.create(null);
  documentAST.definitions.forEach(definition => {
    if (definition.kind === 'OperationDefinition') {
      const variableDefinitions = definition.variableDefinitions;
      if (variableDefinitions) {
        variableDefinitions.forEach(({ variable, type }) => {
          const inputType = typeFromAST(schema, type);
          if (inputType) {
            variableToType[variable.name.value] = inputType;
          }
        });
      }
    }
  });
  return variableToType;
}
Daniel Rearden
  • 80,636
  • 11
  • 185
  • 183