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.
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:
import { useCatsSuspenseQuery } from "./queries.generated"
const response = useCatsSuspenseQuery()
if (!response.data) return undefined<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
}
}
}