GraphQL

Getting Started

Helpful Resources

General resources:

GraphQL at GitLab:

Libraries

We use Apollo (specifically Apollo Client) and Vue Apollo when using GraphQL for frontend development.

If you are using GraphQL in a Vue application, the Usage in Vue section can help you learn how to integrate Vue Apollo.

For other use cases, check out the Usage outside of Vue section.

We use Immer for immutable cache updates; see Immutability and cache updates for more information.

Tooling

Apollo GraphQL VS Code extension

If you use VS Code, the Apollo GraphQL extension supports autocompletion in .graphql files. To set up the GraphQL extension, follow these steps:

  1. Generate the schema: bundle exec rake gitlab:graphql:schema:dump
  2. Add an apollo.config.js file to the root of your gitlab local directory.
  3. Populate the file with the following content:

     module.exports = {
       client: {
         includes: ['./app/assets/javascripts/**/*.graphql', './ee/app/assets/javascripts/**/*.graphql'],
         service: {
           name: 'GitLab',
           localSchemaFile: './tmp/tests/graphql/gitlab_schema.graphql',
         },
       },
     };
    
  4. Restart VS Code.

Exploring the GraphQL API

Our GraphQL API can be explored via GraphiQL at your instance’s /-/graphql-explorer or at GitLab.com. Consult the GitLab GraphQL API Reference documentation where needed.

You can check all existing queries and mutations on the right side of GraphiQL in its Documentation explorer. You can also write queries and mutations directly on the left tab and check their execution by clicking Execute query button on the top left:

GraphiQL interface

Apollo Client

To save duplicated clients getting created in different apps, we have a default client that should be used. This sets up the Apollo client with the correct URL and also sets the CSRF headers.

Default client accepts two parameters: resolvers and config.

  • resolvers parameter is created to accept an object of resolvers for local state management queries and mutations
  • config parameter takes an object of configuration settings:
    • cacheConfig field accepts an optional object of settings to customize Apollo cache
    • baseUrl allows us to pass a URL for GraphQL endpoint different from our main endpoint (for example, ${gon.relative_url_root}/api/graphql)
    • fetchPolicy determines how you want your component to interact with the Apollo cache. Defaults to “cache-first”.

Multiple client queries for the same object

If you are making multiple queries to the same Apollo client object you might encounter the following error: Cache data may be lost when replacing the someProperty field of a Query object. To address this problem, either ensure all objects of SomeEntityhave an id or a custom merge function. We are already checking ID presence for every GraphQL type that has an ID, so this shouldn’t be the case. Most likely, the SomeEntity type doesn’t have an ID property, and to fix this warning we need to define a custom merge function.

We have some client-wide types with merge: true defined in the default client as typePolicies (this means that Apollo will merge existing and incoming responses in the case of subsequent queries). Please consider adding SomeEntity there or defining a custom merge function for it.

GraphQL Queries

To save query compilation at runtime, webpack can directly import .graphql files. This allows webpack to pre-process the query at compile time instead of the client doing compilation of queries.

To distinguish queries from mutations and fragments, the following naming convention is recommended:

  • all_users.query.graphql for queries;
  • add_user.mutation.graphql for mutations;
  • basic_user.fragment.graphql for fragments.

If you are using queries for the CustomersDot GraphQL endpoint, end the filename with .customer.query.graphql, .customer.mutation.graphql, or .customer.fragment.graphql.

Fragments

Fragments are a way to make your complex GraphQL queries more readable and re-usable. Here is an example of GraphQL fragment:

fragment DesignListItem on Design {
  id
  image
  event
  filename
  notesCount
}

Fragments can be stored in separate files, imported and used in queries, mutations, or other fragments.

#import "./design_list.fragment.graphql"
#import "./diff_refs.fragment.graphql"

fragment DesignItem on Design {
  ...DesignListItem
  fullPath
  diffRefs {
    ...DesignDiffRefs
  }
}

More about fragments: GraphQL documentation

Global IDs

The GitLab GraphQL API expresses id fields as Global IDs rather than the PostgreSQL primary key id. Global ID is a convention used for caching and fetching in client-side libraries.

To convert a Global ID to the primary key id, you can use getIdFromGraphQLId:

import { getIdFromGraphQLId } from '~/graphql_shared/utils';

const primaryKeyId = getIdFromGraphQLId(data.id);

It is required to query global id for every GraphQL type that has an id in the schema:

query allReleases(...) {
  project(...) {
    id // Project has an ID in GraphQL schema so should fetch it
    releases(...) {
      nodes {
        // Release has no ID property in GraphQL schema
        name
        tagName
        tagPath
        assets {
          count
          links {
            nodes {
              id // Link has an ID in GraphQL schema so should fetch it
              name
            }
          }
        }
      }
      pageInfo {
        // PageInfo no ID property in GraphQL schema
        startCursor
        hasPreviousPage
        hasNextPage
        endCursor
      }
    }
  }
}

Immutability and cache updates

From Apollo version 3.0.0 all the cache updates need to be immutable. It needs to be replaced entirely with a new and updated object.

To facilitate the