this is my first discussion post here. I have learned Apollo
+ GraphQL
through Odyssey. Currently, I am building my own project using Next.js which required fetching data from 2 GraphQL endpoints.
My problem: How can I fetch data from multiple GraphQL endpoints with ApolloClient
?
Below is my code for my first endpoint:
import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client";
const client = new ApolloClient({
ssrMode: true,
link: createHttpLink({
uri: "https://api.hashnode.com/",
credentials: "same-origin",
headers: {
Authorization: process.env.HASHNODE_AUTH,
},
}),
cache: new InMemoryCache(),
});
export default client;
4
4 Answers
What you are trying to accomplish is kinda against Apollo’s "One Graph" approach.
Take a look at gateways and federation – https://www.apollographql.com/docs/federation/
With that being said, some hacky solution is possible but you will need to maintain a more complex structure and specify the endpoint in every query, which undermines the built-in mechanism and might cause optimization issues.
//Declare your endpoints
const endpoint1 = new HttpLink({
uri: 'https://api.hashnode.com/graphql',
...
})
const endpoint2 = new HttpLink({
uri: 'endpoint2/graphql',
...
})
//pass them to apollo-client config
const client = new ApolloClient({
link: ApolloLink.split(
operation => operation.getContext().clientName === 'endpoint2',
endpoint2, //if above
endpoint1
)
...
})
//pass client name in query/mutation
useQuery(QUERY, {variables, context: {clientName: 'endpoint2'}})
This package seems to do what you want: https://github.com/habx/apollo-multi-endpoint-link
Also, check the discussion here: https://github.com/apollographql/apollo-client/issues/84
6
-
May I know how to add headers?
//Declare your endpoints const endpoint1 = new HttpLink({ uri: 'https://api.hashnode.com/graphql', ... }) const endpoint2 = new HttpLink({ uri: 'endpoint2/graphql', ... }) //pass them to apollo-client config const client = new ApolloClient({ link: ApolloLink.split( operation => operation.getContext().clientName === 'endpoint2', endpoint2, //if above endpoint1 ) ... }) //pass client name in query/mutation useQuery(QUERY, {variables, context: {clientName: 'endpoint2'}})
– EugeneOct 19, 2021 at 14:31
-
I assume in the same way you did before:
const endpoint1 = new HttpLink({ uri: '...', credentials: credentials, headers: headers})
. Doesnt work like this?– Jacek WalasikOct 19, 2021 at 14:49
-
thank you. I solved my problems.
– EugeneOct 19, 2021 at 15:30
-
My headers is not working anymore if I put it in this way:
const endpoint2 = new HttpLink({ uri: "https://api.github.com/graphql", headers: { Authorization:
Bearer ${process.env.GITHUB_ACCESS_TOKEN}, }, });
– EugeneOct 20, 2021 at 6:24
-
1
That's unexpected, maybe incorrect use of Template literals – try
headers: { authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`}
– Jacek WalasikOct 20, 2021 at 8:23
Encountered the same problem today. I wanted to have it dynamic so this is what I came out with:
export type DynamicLinkClientName = "aApp" | "bApp" | "graphqlApp";
type Link = RestLink | HttpLink;
type DynamicLink = { link: Link; name: DynamicLinkClientName };
const LINK_MAP: DynamicLink[] = [
{ link: aRestLink, name: "aApp" },
{ link: bAppRestLink, name: "bApp" },
{ link: graphqlAppLink, name: "graphqlApp" },
];
const isClientFromContext = (client: string) => (op: Operation) =>
op.getContext().client === client;
const DynamicApolloLink = LINK_MAP.reduce<ApolloLink | undefined>(
(prevLink, nextLink) => {
// When no name is specified, fallback to defaultLink.
if (!prevLink) {
return ApolloLink.split(
isClientFromContext(nextLink.name),
nextLink.link,
defaultLink
);
}
return ApolloLink.split(
isClientFromContext(nextLink.name),
nextLink.link,
prevLink
);
},
undefined
) as ApolloLink;
Really like the solution by Pete for allowing more than just 2 endpoints.
decided to write my own version for better type checking.
Here is my take on his solution:
Typescript:
const defaultClient: keyof typeof clients = "heroku";
const clients = {
"heroku": new HttpLink({ uri: "https://endpointURLForHeroku" }),
"lists": new HttpLink({uri: "https://endpointURLForLists" })
}
const isRequestedClient = (clientName: string) => (op: Operation) =>
op.getContext().clientName === clientName;
const ClientResolverLink = Object.entries(clients)
.map(([clientName, Link]) => ([clientName, ApolloLink.from([Link])] as const))
.reduce(([_, PreviousLink], [clientName, NextLink]) => {
const ChainedLink = ApolloLink.split(
isRequestedClient(clientName),
NextLink,
PreviousLink
)
return [clientName, ChainedLink];
}, ["_default", clients[defaultClient]])[1]
declare module "@apollo/client" {
interface DefaultContext {
clientName: keyof typeof clients
}
}
JS:
const defaultClient = "heroku";
const clients = {
"heroku": new HttpLink({ uri: "https://endpointURLForHeroku" }),
"lists": new HttpLink({uri: "https://endpointURLForLists" })
}
const isRequestedClient = (clientName) => (op) =>
op.getContext().clientName === clientName;
const ClientResolverLink = Object.entries(clients)
.reduce(([_, PreviousLink], [clientName, NextLink]) => {
const ChainedLink = ApolloLink.split(
isRequestedClient(clientName),
NextLink,
PreviousLink
)
return [clientName, ChainedLink];
}, ["_default", clients[defaultClient]])[1]
I see this as an Apollo Links task which is part of the vanilla Apollo Client.
Specifically a directional composition link chain
My prototype may be a bit too verbose, so all you need to worry about is:
- use
ApolloLink.split
in the initialisation of your ApolloClient to direct queries to a different endpoint. - redirect in the
Apollo Link
using some boolean operation.- In the example I am using "context" (something you put in your query options).
Note that you don’t need to
Here is my prototype (NextJS + TypeScript + ApolloClient):
libs/apollo/index.ts
(wherever you want to define your apollo client)
import { ApolloClient, ApolloLink, HttpLink, InMemoryCache } from '@apollo/client'
// Declare your endpoints
const animeEndpoint = new HttpLink({
uri: 'https://graphql.anilist.co',
})
const countriesEndpoint = new HttpLink({
uri: 'https://countries.trevorblades.com/',
})
// Not necessary. Just helps with type safety.
export enum Endpoint {
anime = 'anime',
country = 'country',
}
//pass them to apollo-client config
const client = new ApolloClient({
// Version here is just a custom property that we can use to determine which endpoint to use
// Truthy = animeEndpoint (second) parameter, falsy = countriesEndpoint (third) parameter
link: ApolloLink.split((operation) => operation.getContext().version === Endpoint.anime, animeEndpoint, countriesEndpoint),
cache: new InMemoryCache(),
})
export default client
app/page.tsx
(because I am using NextJS for this)
'use client'
import { useQuery } from '@apollo/client'
import GetAnimeQuery from '@/libs/gql/GetAnime' // Just a graphql query
import { Endpoint } from '@/libs/apollo'
import GetCountriesQuery from '@/libs/gql/GetCountries' // Just a graphql query
export default function Home() {
// Anime data
const { loading: loadingAnime, error: errorAnime, data: dataAnime } = useQuery(GetAnimeQuery, { context: { version: Endpoint.anime } })
// Countries data
const {
loading: loadingCountries,
error: errorCountries,
data: dataCountries,
} = useQuery(GetCountriesQuery, { context: { version: Endpoint.country } })
console.log('Countries', dataCountries)
console.log('Anime', dataAnime)
return (
<main>
{loadingAnime && <p>Loading Anime...</p>}
{errorAnime && <p>Error Anime :{errorAnime.message}</p>}
{loadingCountries && <p>Loading Countries...</p>}
{errorCountries && <p>Error Countries:{errorCountries.message}</p>}
</main>
)
}
Appendix (skip me unless you want the graphql query)
libs/gql/GetAnime.ts
import { gql } from '@apollo/client'
const GetAnimeQuery = gql`
query Get {
Page(page: 1, perPage: 5) {
pageInfo {
total
currentPage
lastPage
hasNextPage
perPage
}
media {
id
title {
romaji
}
}
}
}
`
export default GetAnimeQuery
libs/gql/GetCountries.ts
import { gql } from '@apollo/client'
const GetCountriesQuery = gql`
query GetAllCountries {
countries {
code
currency
name
}
}
`
export default GetCountriesQuery
Can you provide your ApolloClient configuration code?
Oct 19, 2021 at 10:27
Hi, here is what I have written for my first client endpoint. I'm planning to add another endpoint but I can't really find any solution on the Internet.
Oct 19, 2021 at 10:48
import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client"; const client = new ApolloClient({ ssrMode: true, link: createHttpLink({ uri: "https://api.hashnode.com/", credentials: "same-origin", headers: { Authorization: process.env.HASHNODE_AUTH, }, }), cache: new InMemoryCache(), }); export default client;
@JacekWalasik Above is my code. 🙂Oct 19, 2021 at 10:49
@JacekWalasik Can you check my comment below? I'm facing issue my headers is not working anymore when I put my token into
.env.local
file & use the token in other file.Oct 20, 2021 at 6:26