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:
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:
- Find out that we have films in the GraphQL API.
- 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:
- The client, using the
introspectionQuery
, queries a server's introspection system for enough information to reproduce that server's type system. - The
buildClientSchema
function creates and returns a GraphQLSchema instance using the results of theintrospectionQuery
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.