API

Apollo

The site uses Apollo client to fetch and populate the site with data.

Setup

The Apollo client setup files can be found in core/libs/apollo

Authorising queries

Some queries will need authorised if they retrieve sensitive information. for example user data. This is done by passing the JWT issued to users when logging in or during user creation in the request header. The API requires the client, access_token and uid values to be passed for requests that require authorisation to succeed.

Writing queries

Queries must be located in their own files using the .gql extensions. These queries can’t be used directly but will be exposed for import in a separate .generated.tsx file after running the codegen task (see below).

File placement guidelines

Component-specific queries

Place queries, mutations, or fragments alongside the component if they are used only within that component/hook. This practice helps in keeping the component/hook and its associated queries together, making the code more maintainable and easier to understand.

Shared queries across Apps

Place queries, mutations, or fragments in the packages/core/libs/apollo directory if they are shared across multiple applications. This centralization promotes reusability and ensures that common queries are easily accessible to all apps, reducing duplication and potential inconsistencies.

App-specific shared queries

Place queries, mutations, or fragments in the apps/__app__/src/apollo directory if they are shared but specific to the app. This approach keeps app-specific logic contained within the app’s directory structure while still allowing for sharing within the app.

ℹ️

Additionally, ensure that each set of files is placed in its own folder. Include an index.ts file in each folder to re-export everything from the .generated file.

      • user.gql
      • user.generated.tsx
      • index.ts

Generating queries and types

Typescript types are automatically generated by running yarn workspace:codegen (workspace being replaced with the name of the current workspace you are working on). The resulting generated files are co-located with their queries.

The codegen command not only outputs schemas but also well typed GraphQL commands that can be imported.

cats.generated.tsx
export function useCatsQuery(baseOptions?: Apollo.QueryHookOptions<CatsQuery, CatsQueryVariables>) {
  ...
}
export function useCatsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<CatsQuery, CatsQueryVariables>) {
  ...
}
export function useCatsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions<CatsQuery, CatsQueryVariables>) {
  ...
}
const response = useCatsSuspenseQuery()

It is preferred that these generated queries are used instead of constructing your own. Write your queries in a separate file, do not export them, and instead used the generated equivalents.

Code generation setup is defined in codegen.ts.

For more information on this setup you can read the documentation here.

Queries

Queries can be fired from both server and client components.

Client components

Fetching with Suspense is preferred when fetching data on the client side where possible. Using the a suspense query hook means loading states such as skeletons can easily be added to the UI while data is being fetched:

component.tsx
import { useCatsSuspenseQuery } from "./queries.generated"
 
const response = useCatsSuspenseQuery()
 
if (!response.data) return undefined
parent.tsx
<Suspense fallback={<Skeleton />}>
  <Component />
</Suspense>

Server components

import { getClient } from "core"
 
const client = getClient()
 
const response = await client.query({
  query: gql`
    query ...
  `
})

More information on how the Apollo client is setup for use in SSR can be found here.

Edge Apollo Client

The Edge Apollo Client is a specialized version of our Apollo client optimized for the Edge Runtime environment, which has different constraints than the Node.js environment.

Key features

  • Compatible with Edge Runtime (middleware, edge functions)
  • No dynamic imports
  • Properly forwards auth headers
  • Network-only fetch policy by default

Usage

import { getEdgeClient } from "packages/core/libs/apollo/EdgeApolloClient"
 
const apolloClient = getEdgeClient()
const result = await apolloClient.query({
  query: YourQueryDocument,
  context: { headers }
})
ℹ️

The Edge Apollo Client is specifically designed for middleware and other Edge Runtime contexts where dynamic imports are not supported. Use this client when working with Next.js middleware or Edge API Routes.

Fragments

Where ever possible queries should be assembled using fragments. Reusing fragments in this way reduces the effort required to keep fields consistent across a set of operations.

Fragment files can be found in core/libs/apollo/fragments.

Stubbing graph fields

When a field is required that is not yet available on the API we can add the field by injecting it into the cache on the front end.

To do this we need to extend the schema with the new field.

extend type AnimalType {
  test: String!
}

And update the type policy to tell the model what to return when the stubbed field is queried.

const typePolicies = {
  AnimalType: {
    fields: {
      test: {
        read() {
          return "This is a test"
        }
      }
    }
  }
}

The new field will now be able to be queried, but should be flagged as a client side field.

query Breeds($id: ID!) {
  animalType(id: $id) {
    test @client
    breeds {
      name
    }
  }
}