Imagine this, you spent day and night working on the perfect web application. You worked together with the backend devs to craft the smoothest and most stable GraphQL endpoint there ever was. Filtering, sorting, paging, all the parts were there. New collection? No problem at all, it's added in no-time. Amazing! Until... It was decided to add a second GraphQL endpoint. Wait what, can't we add it as a collection to the current endpoint? No? But wait, we can do schema stitching, right? Right??
Well the answer was no. Here we are with 2 GraphQL endpoints that both need to be consumed simultaneously. We're using Apollo Client, which by default only supports a single endpoint. Next to that, we're also using the GraphQL Code Generator for converting the schema and operations.graphql
files to Typescript types and documents. A generic, solid solution was mandatory to support both of these libs, so I went ahead and created a structure that you can also reuse in your projects.
Used endpoints
I created a demo application on GitHub. In this demo I consumed data from a free-to-use SpaceX GraphQL endpoint and a countries GraphQL endpoint.
Consumption of data
First I want to show what I ended up with on the consumption part of the application.
const { data: launchesData, loading: launchesLoading } = useSpacexQuery(GetLaunchesDocument, {
variables: {
limit: 5
}
});
const { data: countriesData, loading: countriesLoading } = useCountriesQuery(GetCountriesDocument, {
variables: {
filter: {
continent: {
eq: 'OC'
}
}
}
});
As you can see, instead of the default useQuery
hook from Apollo I now have useSpacexQuery
and useCountriesQuery
hooks. As you can imagine, they both fetch data from their corresponding data source. I made useSpacexMutation
and useCountriesMutation
available as well, I just don't use them in this example.
Setting up Apollo clients
In useApollo.ts I made the hooks available. What I basically do, is create a generic instance of useQuery
and inject the correct Apollo Client based on whether you want SpaceX data or Countries data.
const useGenericQuery =
(type: ApolloClientTypes) =>
<TData = any, TVariables extends OperationVariables = OperationVariables>(
query: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: QueryHookOptions<TData, TVariables>
) => {
const { getClient } = useApolloClient();
const client = getClient(type);
return useQuery(query, {
...options,
client: client
});
};
export const useSpacexQuery = useGenericQuery('spacex');
export const useCountriesQuery = useGenericQuery('countries');
In useApolloClient.ts you can see how to return the correct GraphQL endpoint for your data. By creating 2 Apollo Clients (or more if you need them) you can use either of them in your data calls.
Because we created the custom useXQuery
hook, we don't ever have to think of configuring the Apollo Client again, we can just use it as easily as when you only had one useQuery
hook.
GraphQL Code Generator
I use the GraphQL Code Generator for generating Typescript types and documents based on a schema and operation.grapql
files. Usually that works pretty straight forward if you only have a single endpoint. Since I have 2 of them, I simply added 2 codegen.ts
files.
My countries codegen.ts
file responds to operations.countries.graphql
files, and my SpaceX codegen.ts
file to operations.spacex.graphql
files.
I found this the most straight forward solution to a sub-optimal situation. Hopefully this helps you out as well.
Cheers!