GraphQL Introspection: Querying APIs without a Schema

Ever wondered how to query or get the whole schema from any GraphQL API without knowing the schema beforehand? What did you think GraphQL Introspection was - one of the coolest things about GraphQL? Aha! What if I told you GraphQL APIs comes with built-in API documentation? Mind. Blown.

The GraphQL Introspection Query helps us do these three things very very well:

  1. Query GraphQL APIs without Schema
  2. Get the Whole Schema from a GraphQL Server
  3. Document GraphQL APIs

If you don't know the schema of a GraphQL API you want to query or if you want to get the whole GraphQL schema in one huge query, this guide is what you are looking for.

Let's dive right in.

What is the GraphQL Introspection Query

This is the GraphQL introspection Query:

        
query IntrospectionQuery {
  __schema {
    queryType { name }
    mutationType { name }
    subscriptionType { name }
    types {
      ...FullType
    }
    directives {
      name
      description
      args {
        ...InputValue
      }
      onOperation
      onFragment
      onField
    }
  }
}

fragment FullType on __Type {
  kind
  name
  description
  fields(includeDeprecated: true) {
    name
    description
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
    isDeprecated
    deprecationReason
  }
  inputFields {
    ...InputValue
  }
  interfaces {
    ...TypeRef
  }
  enumValues(includeDeprecated: true) {
    name
    description
    isDeprecated
    deprecationReason
  }
  possibleTypes {
    ...TypeRef
  }
}

fragment InputValue on __InputValue {
  name
  description
  type { ...TypeRef }
  defaultValue
}

fragment TypeRef on __Type {
  kind
  name
  ofType {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
      }
    }
  }
}
        
      

Wait. What?

Querying GraphQL APIs without the Schema

Let's break the introspection query again.

We will run this introspection query against the hello world of GraphQL APIs, whose schema we don't know.

.

If we want to query an API designed by somebody else, we probably don't know the types.

We start by asking GraphQL the types with __schema. This field is available on the root type of a Query.

        
{
  __schema {
    queryType { name }
    mutationType { name }
    subscriptionType { name }
    types {
      name
      description
    }
  }
}
       
      

Try this...

Open https://graphql.org/swapi-graphql/ in your browser to follow along.

This gives us all the types in our schema (shortened here for brevity).

        
{
  "data": {
    "__schema": {
      "queryType": {
        "name": "Root"
      },
      "mutationType": null,
      "subscriptionType": null,
      "types": [
        {
          "name": "Root",
          "description": null
        },
        {
          "name": "String",
          "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text."
        },
        {
          "name": "Int",
          "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. "
        },
        {
          "name": "FilmsConnection",
          "description": "A connection to a list of items."
        },
        {
          ...
        },
        {
          "name": "__DirectiveLocation",
          "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies."
        }
      ]
    }
  }
}
        
      

Any fields preceded by a double underscore e.g. __schema is part of the introspection system. Others include __type, __field etc.

You might have noticed String and Int in the response, this are built-in scalar types as described.

Any other type such as FilmConnection and DirectiveLocation are the ones defined in the type system (often in our schema.js).

Moving on...

If we add a fragment to __Type in an introspective Query as follows:

        
{
  __schema {
    types {
      ...FullType
    }
  }
}

fragment FullType on __Type {
  kind
  name
  description
}
       
      

We get back...

        
{
  "data": {
    "__schema": {
      "types": [
        {
          "kind": "OBJECT",
          "name": "Root",
          "description": null
        },
        {
          "kind": "SCALAR",
          "name": "String",
          "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text."
        },
        {
          "kind": "SCALAR",
          "name": "Int",
          "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. "
        },
        {
          "kind": "OBJECT",
          "name": "FilmsConnection",
          "description": "A connection to a list of items."
        },
        {
          ...
        },
        {
          "kind": "ENUM",
          "name": "__DirectiveLocation",
          "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies."
        }
      ]
    }
  }
}
        
      

kind gives us the enum value for the type, such as OBJECT, SCALAR and ENUM. name gives us the name of the type

Further still...

According to the introspection Query, we expect that types will have name, fields etc. using the FullType fragment.

So a __Type such as Film should have a name and fields.

We also expect that each of these fields will have a name, and a type

So each of the fields of Film should have a kind and a name using the TypeRef amongst other things..

We also expect that each field type of Film has a name and a kind amongst other things.

That's a mouthful :)

We can try this out as follows:

        
{
  __type(name: "Film") {
    name
    fields {
      name
      type {
        ...TypeRef
      }
    }
  }
}

fragment TypeRef on __Type {
    kind
    name
}
        
      

Or simply ...

        
{
  __type(name: "Film") {
    name
    fields {
      name
      type {
        kind
        name
      }
    }
  }
}
        
      

As expected, we get all the fields of a film.

        
{
  "data": {
    "__type": {
      "name": "Film",
      "fields": [
        {
          "name": "title",
          "type": {
            "kind": "SCALAR",
            "name": "String"
          }
        },
        {
          "name": "episodeID",
          "type": {
            "kind": "SCALAR",
            "name": "Int"
          }
        },
        {
          "name": "openingCrawl",
          "type": {
            "kind": "SCALAR",
            "name": "String"
          }
        },
        {
          "name": "director",
          "type": {
            "kind": "SCALAR",
            "name": "String"
          }
        },
        {
          ...
        },
        {
          "name": "id",
          "type": {
            "kind": "NON_NULL",
            "name": null
          }
        }
      ]
    }
  }
}
        
      

To recap...

Using the introspection Query, we are able to:

  1. Find out that we have films in the GraphQL API.
  2. Return all the fields of a film with its name and type.

Are you starting to see the power of the introspection Query?

Getting the Whole Schema from a GraphQL API using introspectionQuery

A common use case of the larger graphql/utilities module is getting the whole schema from a GraphQL Server using the introspectionQuery.

These occurs in two stages:

  1. The client, using the introspectionQuery, queries a server's introspection system for enough information to reproduce that server's type system.
  2. The buildClientSchema function creates and returns a GraphQLSchema instance using the results of the introspectionQuery which can then be used with all GraphQL.js tools

Let's implement this is Node.js.

Using GraphQL.js to Get Whole GraphQL Schema

First install graphql.js tools

        
$ yarn add graphql # or npm install --save graphql
        
      

Then we import the library to in our server.js.

Documenting GraphQL APIs

I will discuss this in a future post and link to it here.