15

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;
juliomalves
  • 42,130
  • 20
  • 150
  • 146
Eugene
  • 163
  • 1
  • 1
  • 6
  • Can you provide your ApolloClient configuration code? – Jacek Walasik Oct 19 '21 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. – Eugene Oct 19 '21 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. :) – Eugene Oct 19 '21 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. – Eugene Oct 20 '21 at 06:26

3 Answers3

16

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

Jacek Walasik
  • 439
  • 3
  • 10
  • 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'}})` – Eugene Oct 19 '21 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 Walasik Oct 19 '21 at 14:49
  • thank you. I solved my problems. – Eugene Oct 19 '21 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}`, }, });` – Eugene Oct 20 '21 at 06:24
  • 1
    That's unexpected, maybe incorrect use of Template literals - try ```headers: { authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`}``` – Jacek Walasik Oct 20 '21 at 08:23
  • For some reason, my requests are also missing headers when doing it this way. However, it is not missing authorization headers. but things like `:path:` and `:authority:`. Any idea what might be causing this? im sharing code of all the middleware and links between both clients – Dominik Reinert Jan 06 '23 at 09:17
2

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;
Pete
  • 31
  • 1
  • 2
1

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]
Master Noob
  • 659
  • 3
  • 9
  • 19