apollographql / apollo-client

:rocket:  A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
https://apollographql.com/client
MIT License
19.38k stars 2.66k forks source link

Allow for multiple remote endpoints #84

Closed jbaxleyiii closed 8 years ago

jbaxleyiii commented 8 years ago

One of things things that has me a little confused, is the seemingly lack of versioning recommended by GraphQL implementation articles and examples. Speaking for our team, we are already planning on making our current implementation essentially a pre release (/graphql) and starting with our apollo server build out at api/v1 or graphql/v1.

That being said, I'm interested to get thoughts on using the apollo client with multiple remote endpoints. Here are a few examples / thoughts on the api design

3rd party service + in house service

Let's say we look into the future a year or so. GraphQL has solidified around a standard (already pretty dang close!) and more and more services offer Graph endpoints along with their standard REST endpoints. So I'm building an app that pulls from my app source, and pulls from reddit for example.

// a vanilla, not external redux store example
import ApolloClient, { createNetworkInterface } from "apollo-client";

const Reddit = createNetworkInterface('https://reddit.com/graphql');
const Heighliner = createNetworkInterface('https://api.newspring.cc/graphql');

// we can either specify the remote locations on the queries
// fwiw, I think this is a bad idea ;) but anything is possible right?
// this would mean only on client instantiated with multiple network interfaces passed to it
`
  @Reddit
  query getCategory($categoryId: Int!) {
      category(id: $categoryId) {
        name
        color
      }
    }
`

`
  @Heighliner
  query getCategory($categoryId: Int!) {
      category(id: $categoryId) {
        name
        color
      }
    }
`

const client = new ApolloClient({
  networkInterface: {
    Reddit,
    Heighliner
  }
})

// or we can create multiple clients but will have to figure out the duplicate store key
const RedditClient = new ApolloClient({
  networkInterface: Reddit,
  reduxRootKey: "reddit",
});

const HeighlinerClient = new ApolloClient({
  networkInterface: Heighliner,
  reduxRootKey: "heighliner",
});

Personally I am a fan of instantiating multiple clients as the two endpoints should have no overlap (if they did, your GraphQL server should be handling the linking, not the client). That also gives us the benefit of doing something like this in the future

Remote service + local service. One query / action language to rule them all

// a vanilla, not external redux store example
import ApolloClient, { createNetworkInterface, createLocalInterface } from "apollo-client";

// localGraphQLResolver is a client side implementation of a graphql app
// it allows for mutations to update / change local state of the app
// and queries to get app state
// @NOTE this is just an idea I've been playing with. Hope to have an example soon
const Local = createLocalInterface(localGraphQLResolver); 
const Heighliner = createNetworkInterface('https://api.newspring.cc/graphql');

const App = new ApolloClient({
  networkInterface: Local,
  reduxRootKey: "local",
});

const HeighlinerClient = new ApolloClient({
  networkInterface: Heighliner,
  reduxRootKey: "heighliner",
});

So all in all, I don't think this requires any API changes, just wanted to bring it up :+1:

stubailo commented 8 years ago

Personally, I think the main strength of GraphQL is having exactly one endpoint. If you're querying multiple GraphQL servers from the same client, I feel like you're doing it wrong (just my opinion at the moment).

I think having a function in the server to proxy part of the schema to a different GraphQL server would be the right approach, so that you maintain the property that you can render your whole UI with one roundtrip.

As for merging client and server data, I have a few ideas for how to do that, but I think that's a special case - it should be exactly one server schema and one client schema IMO.

One main argument for me is that the whole point of GraphQL is to be able to traverse data in the same query, and have schema types that reference each other - just sending two root queries to different servers doesn't really give you any benefit over just initializing two clients IMO.

We have some ideas for how this might work in a future Apollo proxy server, which would let you traverse between schemas at will in a single query, from your schema, to external services, etc.

stubailo commented 8 years ago

seemingly lack of versioning recommended by GraphQL implementation articles and examples

My understanding is that GraphQL is supposed to remove the need for versioning by having deprecated fields. So you can deprecate a field, then track how many clients access that field, and once it drops below a certain level you just remove it. Rather than having different versions of the whole API.

jbaxleyiii commented 8 years ago

@stubailo I haven't played around with deprecated fields. I'll give that a go for our use case. I can see how having the server doing the linking would be a better world too.

http://facebook.github.io/graphql/#sec-Deprecation for reference

Thanks for the input :tada:

stubailo commented 8 years ago

Yeah I would really love to know how that works for you. And it would be cool to collaborate on the proxy thing, I think that would be really valuable, if you could just have your schema say "oh, this is a GitHub user" and then be able to import the whole GitHub API schema and just traverse it.

stubailo commented 8 years ago

Moving this: https://github.com/apollostack/apollo-client/issues/86

jbaxleyiii commented 8 years ago

@stubailo since our move would be to the apollo version of the graphql server, I'm sure you'll hear about it haha.

then be able to import the whole GitHub API schema and just traverse it

The dream. Application libraries that you can import and use for your app

crumhorn commented 8 years ago

We are in the process of rewriting the front end to existing old-school webservices, and it's quite naive to assume that a server will only ever have one endpoint.

In our case we have two SOAP services (yep, old stuff) that provide data, and that's just "how it is" currently. In any case, as Apollo as of this writing doesn't seem to support our scenario as I can only tell apollo to use one endpoint based on my url, here's a quick and dirty copy & paste of what I did to make apollo + express handle this (Using Ang2 RC5 if it matters) in case someone else is interested.

First, on the client side, I set up a middleware so that it takes a passed "variable" and sets it as part of the header, as follows:

const networkInterface = createNetworkInterface('http://localhost:4000/graphql');

networkInterface.use([{
    applyMiddleware(req, next) {
        if (!req.options.headers) {
            req.options.headers = {};
        }

        req.options.headers.service = req.request.variables["service"];

        next();
    }
}]);

In my data provider service on the client, I then set up a basic fetch method like:

private fetchData(service:string, query:any):any {
        return new Promise((resolve, reject) => {
            this.angularApollo.query({
                query: query,
                variables: {
                    service: service
                }
            }).then((graphQLResult) => this.handleGraphQLResponse(graphQLResult, (data) => resolve(data), (errors) => reject(errors)));
        });
    }

This will ensure my passed in "service" string ends up on the request header sent to the server.

Now, on the server, using express, I set up a redirect route that reads the header and sends the request on to either one or the other endpoint based on this header:

graphQLServer.use('/graphql', function (req, res) {
    // fetch service from header
    var service = req.headers["service"];

    var path = '';

    if (service == 'trading') {
        path = '/graphqlTrading'
    }
    else if (service == 'ref') {
        path = '/graphqlRef'
    }
    else {
        res.send('Unknown service in header, or not set. Service was: ' + service);
        return;
    }

    var url = 'http://localhost:4000'+path;

    // console.log("Piping '" + service + "' service " + req.method + " + request to "  +url);
    req.pipe(request(url)).pipe(res);
});

Do note however that bodyParser() can mess up redirects as it modifies the body on the request object and can cause the pipe to hang, in my case the express() I use for the GraphQL server is separate from the one I use for the other Ang2 server-side stuff and I don't include bodyParser unless specifically via:

graphQLServer.use('/graphiqlRef', bodyParser.json(), graphiqlExpress({
    endpointURL: '/graphqlRef'
}));

In my opinion, it would be great if you could load up multiple network interfaces in apollo and at query-time be able to tell it which one I want it to use. I'm sure there's many possible solutions though.

yodacom commented 8 years ago

Were you able to work with ApolloStack in context and access your SOAP services? I have a similar case with using legacy SOAP services that we need to access with our Reactjs/Redux app ..we would like to go about this in the new "ApolloStack way" so further describing your success/experience with experimenting with SOAP using ApolloStack would be greatly appreciated and helpful to all of us!

stubailo commented 8 years ago

I'm pretty confident that the correct way to use GraphQL is to have one server that calls different services in the background. Having multiple ApolloClient instances is the best way to call multiple services.

crumhorn commented 8 years ago

@yodacom, yes, we are successful using GraphQL and NodeJS together with npm module "soap". I wrote a quick and dirty java project to generate the GraphQL schema from a WSDL (requires some manual interaction post-generation). Since this issue is regarding something else, feel free to contact me if you are intesteted in it. I could put it up on github if others also are interested.

helfer commented 8 years ago

@crumhorn I think it would be great if you could put it on GitHub, then we can link to it from here.

crumhorn commented 8 years ago

@helfer, and anyone else interested, here it is:

https://github.com/crumhorn/wsdl2graphql

I wrote up a quick Readme.md for it.

Like I mentioned earlier, it's a very quick and dirty project and I didn't spend much time on it, but it did the job for us, saving us a large amount of time. Do note the "Important" section, as there's two lines in the code that might necessary to change to fit other peoples web services.

I suggest running it from either IntelliJ IDEA (or Eclipse) just to make it easy as getting the right classpath on the command line is (as always) a pain.

yodacom commented 8 years ago

Thanks Emil. Yeah the WSDL's are a great conversion project to get to GraphQL. Thanks for posting it on Github so we can poke around and see how to get these old legacy resources on the shiny new path with GraphQL and ApolloStack!!!

All the best,

Jeremy

Yoda of YodaCom jb@yodacom.com www.yodacom.com

Standard Disclosure: YodaCom is a mobile application architect, strategist, and developer. *“YodaCom is also a DMTA - Digital Marketing Technology Architect and Advisor. *

On Wed, Sep 7, 2016 at 2:17 AM, Emil notifications@github.com wrote:

@helfer https://github.com/helfer, and anyone else interested, here it is:

https://github.com/crumhorn/wsdl2graphql

I wrote up a quick Readme.md for it.

Like I mentioned earlier, it's a very quick and dirty project and I didn't spend much time on it, but it did the job for us, saving us a large amount of time. Do note the "Important" section, as there's two lines in the code that might necessary to change to fit other peoples web services.

I suggest running it from either IntelliJ IDEA (or Eclipse) just to make it easy as getting the right classpath on the command line is (as always) a pain.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/apollostack/apollo-client/issues/84#issuecomment-245208843, or mute the thread https://github.com/notifications/unsubscribe-auth/ABjIwdhGSx-A752bqC8un6rfyPnF5foLks5qnnMagaJpZM4IDeaw .

pasviegas commented 8 years ago

So, I was searching for a "createLocalInterface" (would do the same as https://github.com/relay-tools/relay-local-schema) for apollo-client and I ended up in this issue.

For my app implementation I really need this. As I couldn't find it, I will have to do it. Is there any interest in having this on this project? Should I make a PR or do it in a separate library?

stubailo commented 8 years ago

Please do it in a separate library, you should be able to reuse basically all of the code from that Relay package, I think.

What's the use case for you? Unable to run a GraphQL server so you need to do it on the client?

pasviegas commented 8 years ago

It is app that uses a schema to normalise the data, but actually has drivers to multiple ecommece platforms (which are all rest). So independent of which backend, the app doesn't change, just the bundled driver.

I've found another issue #379, concerning the same thing, it seems like a very common use case and is such a small piece of code. It also helps while prototyping. Please consider doing it in the client itself.

stubailo commented 8 years ago

What piece of code are you looking for in particular? Is it just the following?

import { schema } from './schema';
import { print, graphql } from 'graphql';

// Set to whatever
let root, context, variables;

const networkInterface = {
  query: ({ query, variables }) => graphql(schema, print(query), root, context, variables)
}

Or is it more than just calling a schema on the client?

stubailo commented 8 years ago

I'm trying to find out if this is just something we can document, or if there is a lot of logic involved.

pasviegas commented 8 years ago

Yep, it is just calling the schema on the client, that is all. Maybe documenting is enough though.

ffxsam commented 7 years ago

@stubailo I just thought of this and stumbled upon this issue. I saw GitHub released a GraphQL API, and thought "how would I write a front-end app that connected to my own GraphQL backend, but also used GitHub's API?" I don't know of any way for Apollo to connect to multiple servers.

morgs32 commented 7 years ago

@stubailo any chance you could provide an example of this without going to too much trouble? https://github.com/apollographql/apollo-client/issues/84#issuecomment-242853278

I'm pretty confident that the correct way to use GraphQL is to have one server that calls different services in the background. Having multiple ApolloClient instances is the best way to call multiple services.

adamhaney commented 7 years ago

@stubailo I have a similar use case to @ffxsam. I'm working on creating a prepackaged library that I'd like to distribute for other developers to use (so in my case imagine that I am Github and I'm trying to develop a client library that abstracts away the fact that the underlying implementation of my backend is graphql/apollo).

Is there any way that I can create an HOC that utilizes ApolloProvider or similar without conflicting with code that's outside of the scope of my library? This would allow me to distribute an HOC in a library for other developers to use without accidentally smashing their code. Is there a way that I can change the prop that Apollo provider passes down so I could do something like

<ApolloProvider propName={"data"} client={this.theirClient}>
  <ApolloProvider propName={"dontTouch"} client={this.myLibraryClient}>
  </ApolloProvider>
</ApolloProvider>
ffxsam commented 7 years ago

Maybe I was thinking about it the wrong way, and third party GraphQL APIs should be accessed on the backend, and ingested via resolvers?

stubailo commented 7 years ago

BTW this is our long-term solution to this: https://github.com/apollographql/graphql-tools/pull/382

ahelord commented 6 years ago

@ffxsam hello sam finally the third party's api should be consumed from the resolvers of my backend (own api graphql) ?

abumalick commented 5 years ago

there is also an interesting approach described here:

https://medium.com/open-graphql/apollo-multiple-clients-with-react-b34b571210a5

he passes a different client to the query as a prop..

in19farkt commented 5 years ago

Why can't I specify multiple GraphQL endpoints? Because the main strength of GraphQL is having exactly one endpoint? What if I don't have a single one?

I just want to write a frontend in which I will work with several public GraphQL endpoints. I don’t have my own server and I don’t have the slightest desire to raise the server and bother with the merging of the endpoints I need. And to solve this seemingly trivial task, there is nothing but crutches ...

Elijen commented 4 years ago

Assuming we can stitch data on the server is just plain wrong. You could say the same about REST APIs.

Yes, in perfect world every app has one GraphQL endpoint perfectly designed for its needs. But this almost never happens. Developers in the real world need to deal with legacy data and code.

in19farkt commented 4 years ago

Workaround for using of multiple GraphQL endpoints.

We can use custom directives and write a Link that will select one of the Links to the GraphQL endpoints:

import { ApolloLink } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';

const allowedDirectives = ['dai', 'compound'] as const;
type DirectiveName = (typeof allowedDirectives)[number];

const linkByDirective: Record<DirectiveName | 'default', ApolloLink> = {
  dai: daiLink,
  compound: compoundLink,
  default: daiLink,
};

const link = new ApolloLink(operation => {
  const { query } = operation;

  const definition = getMainDefinition(query);

  const foundDirective =
    'operation' in definition &&
    definition.directives?.find(item =>
      allowedDirectives.includes(item.name.value as DirectiveName),
    );
  const directive: DirectiveName | 'default' = foundDirective
    ? (foundDirective.name.value as DirectiveName)
    : 'default';

  return linkByDirective[directive].request(operation);
});

GraphQL query can be written like this

query Users($first: Int!) @compound {
  users(first: $first) {
    id
    cTokens {
      id
      cTokenBalance
    }
    totalSupplyInEth
    totalBorrowInEth
  }
}

You can also use the graphql-codegen, but then you will need to write queries in separate files and the files will need to be named according to the template. For example, for the compound directive, you can specify such a template - QueryName.compound.graphql. Then the graphql-codegen configuration might look like this:

overwrite: true
generates:
  src/generated/compound-graphql.tsx:
    documents: "./src/**/*.compound.graphql"
    schema: "https://api.thegraph.com/subgraphs/name/compound-finance/compound-v2-rinkeby"
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"
    config:
      withComponent: false
      withHOC: false
      withHooks: true
      reactApolloVersion: 3
      scalars:
        BigInt: string
        BigDecimal: string
        Bytes: string
  src/generated/dai-graphql.tsx:
    documents: "./src/**/*.dai.graphql"
    schema: "https://api.thegraph.com/subgraphs/name/raisehq/dai-kovan"
    # ... other configurations
rodrigofd commented 4 years ago
import { ApolloLink } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';

const allowedDirectives = ['dai', 'compound'] as const;
type DirectiveName = (typeof allowedDirectives)[number];

const linkByDirective: Record<DirectiveName | 'default', ApolloLink> = {
  dai: daiLink,
  compound: compoundLink,
  default: daiLink,
};

Good one 👍 ... small typo:

const link = new ApolloLink(operation => {
  const { query } = operation;

  const definition = getMainDefinition(query);

  const link = new ApolloLink(operation => {
  const { query } = operation;

  const definition = getMainDefinition(query);

You have that code block duplicated ;)

in19farkt commented 4 years ago

@rodrigofd Thanks, updated :)

rodrigofd commented 4 years ago

@in19farkt following up on your idea (love it :)) Say that I need to implement distinct authentication mechanisms on each endpoint... Would you pack it all on a single 'dynamic' apollo link? Or would you put aside a second auth. link that repeats all the logic to sort out the custom directive? I'm torned between those two approaches...

Plus, by the way, I had to add code to remove the directive at the end, otherwise the GQL server complains on it... did it happen to you ?

in19farkt commented 4 years ago

Say that I need to implement distinct authentication mechanisms on each endpoint... Would you pack it all on a single 'dynamic' apollo link? Or would you put aside a second auth. link that repeats all the logic to sort out the custom directive? I'm torned between those two approaches...

I think that a 'dynamic' link should choose a link according to the directive and nothing more. The chosen link should handle authentication logic.

Plus, by the way, I had to add code to remove the directive at the end, otherwise the GQL server complains on it... did it happen to you ?

I had no such problems.

shafqatevo commented 4 years ago

@in19farkt Does the Split method described here help solve this issue as well?

https://www.apollographql.com/docs/link/composition/

in19farkt commented 4 years ago

@shafqatevo for only 2 Graphql endpoints. If there are more endpoints, then the Split becomes uncomfortable.

EarthlingDavey commented 4 years ago

A working solution using multiple links in your one Apollo client.

https://www.loudnoises.us/next-js-two-apollo-clients-two-graphql-data-sources-the-easy-way/

sbussard commented 4 years ago

UPDATE This approach is WAY EASIER than adding directives

in your setup

const link = new RetryLink().split(
  (operation) => operation.getContext().endpointA,
  new HttpLink({ uri: ENDPOINT_A }),
  new RetryLink().split(
    (operation) => operation.getContext().endpointB,
    new HttpLink({ uri: ENDPOINT_B }),
    new HttpLink({ uri: DEFAULT_ENDPOINT })
  )
);

in your component

  const { data, loading, error } = useQuery(YOUR_QUERY, {
    context: { endpointA: true }
  });

ORIGINAL Here's modified version of @in19farkt's solution. The main difference is that on the client side it tries to remove the directive before sending it so that no further work is needed on the server side.

import { ApolloLink } from 'apollo-link'; // or from @apollo/client
import { getMainDefinition } from 'apollo-utilities';

const links = {
  account: new HttpLink({ uri: 'http://localhost/graphql1' }),
  default: new HttpLink({ uri: 'http://localhost/graphql2' })
};

const link = new ApolloLink(operation => {
  const definition = getMainDefinition(operation.query);
  let endpoint = 'default';

  if ('operation' in definition) {
    const foundDirective = definition.directives?.find(item =>
      Object.keys(links).includes(item.name.value)
    );

    if (foundDirective) {
      endpoint = foundDirective.name.value;

      // remove the directive from the request
      operation.query.definitions[0].directives = operation.query.definitions[0].directives.filter(
        dir => dir.name.value !== endpoint
      );

      operation.query.loc.source.body = operation.query.loc.source.body.replace(
        `@${endpoint} `,
        ''
      );
    }
  }

  return links[endpoint].request(operation);
});
jean9696 commented 4 years ago

Hi there, thank you all for your inputs it was very useful for us. Because we have multiple clients we created a library to handle the case of this issue. Feel free to use it or add issues 🙂 https://github.com/habx/apollo-multi-endpoint-link

mario-petrovic commented 4 years ago

UPDATE This approach is WAY EASIER than adding directives

in your setup

const link = new RetryLink().split(
  (operation) => operation.getContext().endpointA,
  new HttpLink({ uri: ENDPOINT_A }),
  new RetryLink().split(
    (operation) => operation.getContext().endpointB,
    new HttpLink({ uri: ENDPOINT_B }),
    new HttpLink({ uri: DEFAULT_ENDPOINT })
  )
);

in your component

  const { data, loading, error } = useQuery(YOUR_QUERY, {
    context: { endpointA: true }
  });

ORIGINAL Here's modified version of @in19farkt's solution. The main difference is that on the client side it tries to remove the directive before sending it so that no further work is needed on the server side.

import { ApolloLink } from 'apollo-link'; // or from @apollo/client
import { getMainDefinition } from 'apollo-utilities';

const links = {
  account: new HttpLink({ uri: 'http://localhost/graphql1' }),
  default: new HttpLink({ uri: 'http://localhost/graphql2' })
};

const link = new ApolloLink(operation => {
  const definition = getMainDefinition(operation.query);
  let endpoint = 'default';

  if ('operation' in definition) {
    const foundDirective = definition.directives?.find(item =>
      Object.keys(links).includes(item.name.value)
    );

    if (foundDirective) {
      endpoint = foundDirective.name.value;

      // remove the directive from the request
      operation.query.definitions[0].directives = operation.query.definitions[0].directives.filter(
        dir => dir.name.value !== endpoint
      );

      operation.query.loc.source.body = operation.query.loc.source.body.replace(
        `@${endpoint} `,
        ''
      );
    }
  }

  return links[endpoint].request(operation);
});

@sbussard

Thanks to you and the original solution creator in19farkt, this almost worked for me.

  1. Where i have a problem is when i lets say, i do login graphQL call and use this solution that removes directive so that BE does not need to handle that. Login works.
  2. After this i logout and come to login page.
  3. When i use again that same graphQL call for login, and debug your ApolloLink, there is no directive present there. So call is made as if i didn't set directive, in my case @open but name is not relevant.

So my question is, does your solution remove that directive for good? Because in my implementation it does.

And also if you can add an example where you can combine this solution ApolloLink with onError, because your solution does not do forward(operation) as linked error handler expects. Or to chain it with any other ApolloLink when provided as:

   client.current = new ApolloClient({
      link: from([linkWithModificationForDirective, someOtherHandling]),
      cache: new InMemoryCache(),
    });

Thanks

karnash commented 4 years ago

Thanks to you and the original solution creator in19farkt, this almost worked for me too. but when i return to view always set default, and dont take the endpoint in query, in my case the Mutation. image how i can.. set the endpoint before to make the mutation or query. ?? image i using thats gql.. thk to our help.

karnash commented 4 years ago

well i solve that.. using a context var.... on each useQuery or Mutation like that const [createMutate, {loading, error, data}] = useMutation(mutateGQL, { context: { endpoint: 'myendpoint' }, });

and in the App.js i use

if (endpoint == 'default') { const ctx = operation.getContext(); if ('endpoint' in ctx) { endpoint = ctx.endpoint; } } return links[endpoint].request(operation);

karnash commented 4 years ago

but.. now... how.. i can send the header..!! ??? some idea....!! ..

antekai commented 3 years ago

if you want to combine additive and directional composition with ApolloLink.from() and ApolloLink.split() you can:

import {RestLink} from 'apollo-link-rest';
import {ApolloLink} from 'apollo-link';
import {HttpLink} from 'apollo-link-http';
import {InMemoryCache} from 'apollo-cache-inmemory';

const restLink = new RestLink()
const graphql1 = new HttpLink({uri: 'http://localhost/graphql1'});
const graphql2 = new HttpLink({uri: 'http://localhost/graphql2});

const graphqlEndpoints = ApolloLink.split(
  operation => operation.getContext().serviceName === 'service2',
  graphql1,
  graphql2,
);

const link = ApolloLink.from([restLink, graphqlEndpoints]);
const cache = new InMemoryCache();

const Client = new ApolloClient({link,cache});

export default Client;

You can use "service2" endpoint as follows

const {data} = useQuery(SOME_QUERY, { context: {serviceName: 'service2'} });
langarus commented 3 years ago

For other people coming here to find a solution - this part of the docs should solve this problem, the headers can be attached after uri in new HttpLink https://www.apollographql.com/docs/react/api/link/introduction/#providing-to-apollo-client

EugeneGohh commented 3 years ago

For other people coming here to find a solution - this part of the docs should solve this problem, the headers can be attached after uri in new HttpLink https://www.apollographql.com/docs/react/api/link/introduction/#providing-to-apollo-client

I am using Next.js and I followed this by adding headers under uri in new HttpLink but it's not working. Fyi, my headers is for GitHub access token. Do you have any ideas on how to get away with this?

langarus commented 3 years ago

@EugeneGohh That's exactly how it should be done, and how I did it as well. For headers you need to use the context link

https://www.apollographql.com/docs/react/api/link/apollo-link-context/

EugeneGohh commented 3 years ago

@langarus Thanks for replying, I did it in this way but I got an error saying status code 401.

const endpoint2 = new HttpLink({
  uri: "https://api.github.com/graphql",
});

const authLink = setContext((_, { headers }) => {
  return {
    headers: {
      ...headers,
      authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`,
    },
  };
}).concat(endpoint2);

const client = new ApolloClient({
  ssrMode: true,
  link: ApolloLink.split(
    (operation) => operation.getContext().clientName === "authLink",
    authLink,
    endpoint1
  ),
  cache: new InMemoryCache(),
});
langarus commented 3 years ago

I think you should remove .concat(endopint2). And rather

in client

  link: ApolloLink.split(
    (operation) => operation.getContext().clientName === "authLink",
    authLink,
    from([authLink, endpoint1])

    from([authLink, endpoint2])

),

In case you want to remove the header from one endpoint in the link just pass it directly without "from"

On Wed, Oct 20, 2021 at 9:41 AM Eugene Goh @.***> wrote:

@langarus https://github.com/langarus Thanks for replying, I did it in this way but I got an error saying status code 401.

const endpoint2 = new HttpLink({ uri: "https://api.github.com/graphql", });

const authLink = setContext((_, { headers }) => { return { headers: { ...headers, authorization: Bearer ${process.env.GITHUB_ACCESS_TOKEN}, }, }; }).concat(endpoint2);

const client = new ApolloClient({ ssrMode: true, link: ApolloLink.split( (operation) => operation.getContext().clientName === "authLink", authLink, endpoint1 ), cache: new InMemoryCache(), });

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/apollographql/apollo-client/issues/84#issuecomment-947373641, or unsubscribe https://github.com/notifications/unsubscribe-auth/AK3JO2R7QIATPZMRDSDORMDUHZP75ANCNFSM4CAN42YA . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

EugeneGohh commented 3 years ago

I think you should remove .concat(endopint2). And rather in client link: ApolloLink.split( (operation) => operation.getContext().clientName === "authLink", authLink, from([authLink, endpoint1]) from([authLink, endpoint2]) ), In case you want to remove the header from one endpoint in the link just pass it directly without "from" On Wed, Oct 20, 2021 at 9:41 AM Eugene Goh @.***> wrote: @langarus https://github.com/langarus Thanks for replying, I did it in this way but I got an error saying status code 401. const endpoint2 = new HttpLink({ uri: "https://api.github.com/graphql", }); const authLink = setContext((_, { headers }) => { return { headers: { ...headers, authorization: Bearer ${process.env.GITHUB_ACCESS_TOKEN}, }, }; }).concat(endpoint2); const client = new ApolloClient({ ssrMode: true, link: ApolloLink.split( (operation) => operation.getContext().clientName === "authLink", authLink, endpoint1 ), cache: new InMemoryCache(), }); — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#84 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AK3JO2R7QIATPZMRDSDORMDUHZP75ANCNFSM4CAN42YA . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

It's still the same after removing. Do you have any samples how you do it?

EugeneGohh commented 3 years ago

@langarus Hey I just fixed my problem. Thanks.

If anyone needs it:

I fixed it by configuring environment variables in next.config.js. Feel free to check this out https://nextjs.org/docs/api-reference/next.config.js/environment-variables .