Working with GraphQL queries

The webapp and browser extension interact with our backend through a strongly typed GraphQL API. We auto-generate TypeScript types for the schema and all queries, mutations and fragments to ensure the client uses the API correctly.

Writing a type-safe GraphQL query

Write GraphQL queries in plain template strings and tag them with the gql template string tag. Each query must also have a globally unique name. This makes sure the query gets picked up by the type generation and you receive autocompletion, syntax highlighting, hover tooltips and validation.

The preferred way to get a typed result from a GraphQL query or mutation is to pass the auto-generated interface for the query result and variables as type parameters to requestGraphQL():

import { DemoResult, DemoVariables} from '../graphql-operations'

requestGraphQl<DemoResult, DemoVariables>(gql`
    query Demo($input: String) {
        foo(input: $input) {
            bar
        }
    }
`, {
    input: 'Hello world'
})

Note: A lot of older code uses the non-generic queryGraphQl() and mutateGraphQl() functions. These are less type-safe, because they return schema types with all fields of the schema present, no matter whether they were queried or not.

Writing a React component or function that takes an API object as input

React components are often structured to display a subfield of a query. The best way to declare this input type is to define a GraphQL fragment with the component, then using the auto-generated type for that fragment. This ensures the parent component don't forget to query a required field and it makes it easy to hard-code stub results in tests.

import { PersionFields } from '../graphql-operations'

export const personFields = gql`
    fragment PersonFields on Person {
        name
    }
`

export const Greeting: React.FunctionComponent<{ person: PersionFields }> = ({ person }) =>
    <div>Hello, {person.name}!</div>

Since the fragment is exported, parent components can use it in their queries to include the needed data:

import { personFields } from './greeting',

requestGraphQl<PeopleResult>(gql`
    query People {
        people {
            nodes {
                ...PersonFields
            }
        }
    }
    ${personFields}
`)

Note: A lot of older components still use all-fields types generated from the whole schema (as opposed to from a fragment), usually referenced from the namespace GQL.*. This is less safe as fields could be missing from the actual queries and it makes testing harder as hard-coded results need to be casted to the whole type. Some components also worked around this by redeclaring the type structure with complex Pick<T, K> expressions. When you need to interface with these, consider refactoring them to use a fragment type instead.