Overview
In this section, we will:
- Learn what introspection is, and which tools depend on it
- Explore the schema with introspection queries
- Ask for the type of any object with
__typename
Objective 1: Ask the schema about itself
What is introspection?
Introspection lets you query a GraphQL API about its own schema: what types it has, what fields each type has, and what arguments each field takes. You send it to the same endpoint, as an ordinary query. Introspection is part of the GraphQL specification.
Not every API you meet will answer, though. Turning introspection off in production is common, so attackers can’t use it to map the whole API. Apollo Server, which this course uses, turns it off by default when NODE_ENV is production. The course API turns it back on so that Sandbox works. You’ll learn to turn it off in GraphQL 102.
Introspection is how Apollo Sandbox knows what to show you. When Sandbox connects, it sends an introspection query and builds the Documentation panel and autocomplete from the answer. Code generators, editor plugins, and API gateways use it the same way.
Introspection queries use special fields that start with two underscores:
__schema: the whole schema, including every type and the entry points (queryType,mutationType)__type(name:): one type, by name
For example, this query lists every field on Query:
{
__schema {
queryType {
fields {
name
}
}
}
}
{
"data": {
"__schema": {
"queryType": {
"fields": [{ "name": "policies" }, { "name": "policy" }, { "name": "policyholders" }, { "name": "policyholder" }]
}
}
}
}
Reading a field’s type
In the schema, a type like [Policy!]! reads as one piece. Introspection breaks it into layers. NON_NULL (the !) and LIST (the [ ]) are wrappers with no name of their own, and ofType points to what they wrap:
| Schema | Introspection |
|---|---|
String |
SCALAR named String |
String! |
NON_NULL → SCALAR named String |
[Policy!]! |
NON_NULL → LIST → NON_NULL → OBJECT named Policy |
Each field in the introspection result has a kind (SCALAR, OBJECT, ENUM, LIST, NON_NULL, and so on) and a name. Wrappers have a name of null.
Exercise
Imagine you’ve just joined the team that owns this API, and you want to know what a Policyholder looks like without reading the server’s code.
✏️ In Apollo Sandbox, write one query that returns the name of every field on Policyholder, with each field’s type. Your response should be:
{
"data": {
"__type": {
"name": "Policyholder",
"fields": [
{ "name": "id", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "ID" } } },
{ "name": "name", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String" } } },
{ "name": "email", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", "name": "String" } } },
{ "name": "policies", "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "LIST", "name": null } } }
]
}
}
}
Hint: Autocomplete works inside introspection queries too. Start typing __type and see what Sandbox suggests.
Solution
Click to see the solution
{
__type(name: "Policyholder") {
name
fields {
name
type {
kind
name
ofType {
kind
name
}
}
}
}
}
Every Policyholder field is required, so every type is a NON_NULL wrapper, and the real type is one level down in ofType.
policies stops at LIST, because [Policy!]! is wrapped more deeply: NON_NULL → LIST → NON_NULL → Policy. Reaching the name Policy takes two more ofType levels. That’s why tools that introspect a whole schema send much longer queries than this one.
Objective 2: Find out an object’s type
__typename
__typename returns the name of an object’s type. Unlike __schema and __type, you can ask for it on any object, in any query:
{
policies(type: AUTO) {
__typename
policyNumber
policyholder {
__typename
name
}
}
}
{
"data": {
"policies": [
{ "__typename": "Policy", "policyNumber": "AUTO-100001", "policyholder": { "__typename": "Policyholder", "name": "Maria Alvarez" } },
{ "__typename": "Policy", "policyNumber": "AUTO-100003", "policyholder": { "__typename": "Policyholder", "name": "James Okafor" } }
]
}
}
Here the answer is obvious, but __typename matters in two places:
- When a field can return more than one type. You’ll see this in GraphQL 102, where a mutation can return either a claim or a problem.
- In client-side caches. Apollo Client, for example, stores each object under an ID made from its
__typenameandid, likePolicy:p1. That’s how it knows two responses contain the same policy.
Next steps
Next, we’ll look inside the server at the schema and resolvers, and add the missing riskTier argument ourselves.