Cache GraphQL Schema
📈

Cache GraphQL Schema

Created
Apr 8, 2022 8:13 AM
Department
Engineering
Category
Deployment
Technology
GraphQL
Tags
Date
URL

Angular

Caching Graphql Schema for Introspection Fragment Matching

Creating schemaQuery.ts file

Create a file named schemaQuery.ts in the src folder of your application and paste the code below.

  if (!process.env.APP_ENVIRONMENT) {
    require('dotenv').config();
  }

  const fs = require('fs');
  const fetchReq = require('node-fetch');
  const env = process.env.APP_ENVIRONMENT;
  let apiUrl = <<Staging URL>>;

  if (env === 'prod') {
    apiUrl = <<Prod URL>>';
  }

  fetchReq(`${apiUrl}/graphql`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      variables: {},
      query: `
        {
          __schema {
            types {
              kind
              name
              possibleTypes {
                name
              }
            }
          }
        }
      `,
    }),
  })
  .then(result => result.json())
  .then(result => {
    // here we're filtering out any type information unrelated to unions or interfaces
    const filteredData = result.data.__schema.types.filter(
      type => type.possibleTypes !== null,
    );
    result.data.__schema.types = filteredData;
    fs.writeFileSync('./src/assets/fragmentTypes.json', JSON.stringify(result.data), err => {
      if (err) {
        console.error('Error writing fragmentTypes file', err);
      } else {
        console.log('Fragment types successfully extracted!');
      }
    });
  }, err => {
    console.error('Error fetching request', err);
  });
  • For projects that require an access_token to fetch the schema query, you may need to get it from the .env file. Here, we’re just taking the environment from the .env file.

The above code will fetch the schema, filter out the possible union or interface types and write it onto a file called fragmentTypes.json in the assets folder.

Update package.json file

After creating the schemaQuery.ts file, go to the package.json file of your frontend app and update the script to run the schemaQuery.ts file during build time.

"scripts": {
  "build": "npm run build-fragment && ng build --configuration=production",
  "build-fragment": "node src/schemaQuery.ts"
}
  • Always ensure the build-fragment command runs before the build command.

Update Graphql Service

Now, update the graphql.service.ts file or its equivalent file handling the Apollo Initialization, to query the cached schema in fragmentTypes.json for Introspection Fragment Matching.

buildFragmentMatcher(): Promise<any> {
  return new Promise(async (resolve, reject) => {
    let fragmentMatcher;

    try {
      this.schema = await this.fetchSchema();
      if (!this.schema) {
                  return reject(new Error('Schema not found'));
              }
      fragmentMatcher = new IntrospectionFragmentMatcher({
        introspectionQueryResultData: this.schema
      });
      return resolve(fragmentMatcher);
    } catch (e) {
      return reject();
    }
  });
}

async fetchSchema() {
  return new Promise((resolve, reject) => {
    this.http.get('./assets/fragmentTypes.json')
    .subscribe(async res => {
      const response = await res;
      resolve(response);
    }, async error => {
        console.log(error);
        console.log('Fetching schema again..');
        const res = await this.fetchSchemaAtRuntime().catch(e => {
        reject(null);
      });
      if (res) {
        resolve(res);
      }
      reject(null);
    });
  });
}

fetchSchemaAtRuntime() {
  const token = JSON.parse(this.cookies.get('access-token') || null);

  let uri;
  if (token && token.access_token) {
    uri = `${this.environment.api}/graphql?access_token=${token.access_token}`;
  } else {
    uri = `${this.environment.api}/graphql`;
  }

  uri = `${this.environment.api}/graphql`;

  const apollo = new ApolloClient({
    cache: new InMemoryCache,
    link: this.httpLink.create({ uri })
  });


  const query = gql`
    {
      __schema {
        types {
          kind
          name
          possibleTypes {
            name
          }
        }
      }
    }
  `;

  return new Promise((resolve, reject) => {
    apollo.query({
      query,
      variables: {}
    })
      .then((res: any) => {
        // here we're filtering out any type information unrelated to unions or interfaces
        const filteredData = res.data.__schema.types.filter(
          type => type.possibleTypes !== null
        );
        const result = JSON.parse(JSON.stringify(res.data));
        result.__schema.types = filteredData;
        resolve(result);
      }, err => {
        reject(err);
      });
  });
}
  • Here we updated the fetchSchema() method to fetch from the cache first.
  • In case the fragmentTypes.json does not exist or has any exceptions while sending the initial request, we’ll try sending the request again at runtime through fetchSchemaAtRuntime() method.

Add fragmentTypes.json to Git Ignore

Finally, we add the fragmentTypes.json file in Git Ignore.

src/assets/fragmentTypes.json

Errors

In case there are any errors pertaining to node-fetch, do an npm install of that package.

Update Travis and AWS (If .env has been updated)

Update the environment variables in Travis and AWS if there were any changes made to the .env file.