1---
2title: Schema Awareness
3order: 5
4---
5
6# Schema Awareness
7
8Previously, [on the "Normalized Caching" page](./normalized-caching.md) we've seen how Graphcache
9stores normalized data in its store and how it traverses GraphQL documents to do so. What we've seen
10is that just using the GraphQL document for traversal, and the `__typename` introspection field
11Graphcache is able to build a normalized caching structure that keeps our application up-to-date
12across API results, allows it to store data by entities and keys, and provides us configuration
13options to write [manual cache updates](./cache-updates.md) and [local
14resolvers](./local-resolvers.md).
15
16While this is all possible without any information about a GraphQL API's schema, the `schema` option
17on `cacheExchange` allows us to pass an introspected schema to Graphcache:
18
19```js
20const introspectedSchema = {
21 __schema: {
22 queryType: { name: 'Query' },
23 mutationType: { name: 'Mutation' },
24 subscriptionType: { name: 'Subscription' },
25 },
26};
27
28cacheExchange({ schema: introspectedSchema });
29```
30
31In GraphQL, [APIs allow for the entire schema to be
32"introspected"](https://graphql.org/learn/introspection/), which are special GraphQL queries that
33give us information on what the API supports. This information can either be retrieved from a
34GraphQL API directly or from the GraphQL.js Schema and contains a list of all types, the types'
35fields, scalars, and other information.
36
37In Graphcache we can pass this schema information to enable several features that aren't enabled if
38we don't pass any information to this option:
39
40- Fragments will be matched deterministically: A fragment can be written to be on an interface type
41 or multiple fragments can be spread for separate union'ed types in a selection set. In many cases,
42 if Graphcache doesn't have any schema information then it won't know what possible types a field
43 can return and may sometimes make a guess and [issue a
44 warning](./errors.md#16-heuristic-fragment-matching). If we pass Graphcache a `schema` then it'll
45 be able to match fragments deterministically.
46- A schema may have non-default names for its root types; `Query`, `Mutation`, and `Subscription`.
47 The names can be changed by passing `schema` information to `cacheExchange` which is important
48 if the root type appears elsewhere in the schema, e.g. if the `Query` can be accessed on a
49 `Mutation` field's result.
50- We may write a lot of configuration for our `cacheExchange` but if we pass a `schema` then it'll
51 start checking whether any of the configuration options actually don't exist, maybe because we've
52 typo'd them. This is a small detail but can make a large difference in a longer configuration.
53- Lastly; a schema contains information on **which fields are optional or required**. When
54 Graphcache has a schema it knows optional fields that may be left out, and it'll be able to generate
55 "partial results".
56
57### Partial Results
58
59As we navigate an app that uses Graphcache we may be in states where some of our data is already
60cached while some aren't. Graphcache normalizes data and stores it in tables for links and records for
61each entity, which means that sometimes it can maybe even execute a query against its cache that it
62hasn't sent to the API before.
63
64[On the "Local Resolvers" page](./local-resolvers.md#resolving-entities) we've seen how to write
65resolvers that resolve entities without having to have seen a link from an API result before. If
66Graphcache uses these resolvers and previously cached data we often run into situations where a
67"partial result" could already be generated, which is what Graphcache does when it has `schema`
68information.
69
70
72
73Without a `schema` and information on which fields are optional, Graphcache will consider a "partial
74result" as a cache miss. If we don't have all the information for a query then we can't execute
75it against the locally cached data after all. However, an API's schema contains information on which
76fields are required and optional, and if our apps are typed with this schema and
77TypeScript, can't we then use and handle these partial results before a request is sent to the API?
78
79This is the idea behind "Schema Awareness" and "Partial Results". When Graphcache has `schema`
80information it may give us partial results [with the `stale` flag
81set](../api/core.md#operationresult) while it fetches the full result from the API in the
82background. This allows our apps to show some information while more is loading.
83
84## Getting your schema
85
86But how do you get an introspected `schema`? The process of introspecting a schema is running an
87introspection query on the GraphQL API, which will give us our `IntrospectionQuery` result. So an
88introspection is just another query we can run against our GraphQL APIs or schemas.
89
90As long as `introspection` is turned on and permitted, we can download an introspection schema by
91running a normal GraphQL query against the API and save the result in a JSON file.
92
93```js
94import { getIntrospectionQuery } from 'graphql';
95import fetch from 'node-fetch'; // or your preferred request in Node.js
96import * as fs from 'fs';
97
98fetch('http://localhost:3000/graphql', {
99 method: 'POST',
100 headers: { 'Content-Type': 'application/json' },
101 body: JSON.stringify({
102 variables: {},
103 query: getIntrospectionQuery({ descriptions: false }),
104 }),
105})
106 .then(result => result.json())
107 .then(({ data }) => {
108 fs.writeFile('./schema.json', JSON.stringify(data), err => {
109 if (err) {
110 console.error('Writing failed:', err);
111 return;
112 }
113 console.log('Schema written!');
114 });
115 });
116```
117
118Alternatively, if you're already using [GraphQL Code Generator](https://graphql-code-generator.com/)
119you can use [their `@graphql-codegen/introspection`
120plugin](https://graphql-code-generator.com/docs/plugins/introspection) to do the same automatically
121against a local schema. Furthermore it's also possible to
122[`execute`](https://graphql.org/graphql-js/execution/#execute) the introspection query directly
123against your `GraphQLSchema`.
124
125## Optimizing a schema
126
127An `IntrospectionQuery` JSON blob from a GraphQL API can without modification become quite large.
128The shape of this data is `{ "__schema": ... }` and this _schema_ data will contain information on
129all directives, types, input objects, scalars, deprecation, enums, and more. This can quickly add up and one of the
130largest schemas, the GitHub GraphQL API's schema, has an introspection size of about 1.1MB, or about
13150KB gzipped.
132
133However, we can use the `@urql/introspection` package's `minifyIntrospectionQuery` helper to reduce
134the size of this introspection data. This helper strips out information on directives, scalars,
135input types, deprecation, enums, and redundant fields to only leave information that _Graphcache_
136actually requires.
137
138In the example of the GitHub GraphQL API this reduces the introspected data to around 20kB gzipped,
139which is much more acceptable.
140
141### Installation & Setup
142
143First, install the `@urql/introspection` package:
144
145```sh
146yarn add @urql/introspection
147# or
148npm install --save @urql/introspection
149```
150
151You'll then need to integrate it into your introspection script or in another place where it can
152optimise the introspection data. For this example, we'll just add it to the fetching script from
153[above](#getting-your-schema).
154
155```js
156import { getIntrospectionQuery } from 'graphql';
157import fetch from 'node-fetch'; // or your preferred request in Node.js
158import * as fs from 'fs';
159
160import { getIntrospectedSchema, minifyIntrospectionQuery } from '@urql/introspection';
161
162fetch('http://localhost:3000/graphql', {
163 method: 'POST',
164 headers: { 'Content-Type': 'application/json' },
165 body: JSON.stringify({
166 variables: {},
167 query: getIntrospectionQuery({ descriptions: false }),
168 }),
169})
170 .then(result => result.json())
171 .then(({ data }) => {
172 const minified = minifyIntrospectionQuery(getIntrospectedSchema(data));
173 fs.writeFileSync('./schema.json', JSON.stringify(minified));
174 });
175```
176
177The `getIntrospectionSchema ` doesn't only accept `IntrospectionQuery` JSON data as inputs, but also
178allows you to pass a JSON string, `GraphQLSchema`, or GraphQL Schema SDL strings. It's a convenience
179helper and not needed in the above example.
180
181## Integrating a schema
182
183Once we have a schema that's already saved to a JSON file, we can load it and pass it to the
184`cacheExchange`'s `schema` option:
185
186```js
187import schema from './schema.json';
188
189const cache = cacheExchange({ schema });
190```
191
192It may be worth checking what your bundler or framework does when you import a JSON file. Typically
193you can reduce the parsing time by making sure it's turned into a string and parsed using
194`JSON.parse`