apollographql / apollo-client-nextjs

Apollo Client support for the Next.js App Router
https://www.npmjs.com/package/@apollo/experimental-nextjs-app-support
MIT License
358 stars 25 forks source link

How to modify link uri if getClient can't receive arguments? #273

Closed reilnuud closed 1 month ago

phryneas commented 1 month ago

Can you maybe provide a bit of code showcasing how you did it until now and where you were getting that url from in the first place?

Also, what was your expectation it would do? Return the same Apollo Client and just change the url? Return a new Apollo Client each call? Return a limited number of distinct Apollo Client instances?

reilnuud commented 1 month ago

Sorry, I didn't mean to hit submit here since this is a q and not an issue and just meant to ask in the discord, but thanks for the quick reply!

We are using a CMS that appends a url parameter to get draft content -- we were passing an argument to getClient to do this (bad, I know) but now that getClient throws an error when this is done, how do we modify the uri for a request ad-hoc?

So normally a url would look like:

Normal request: https://cms.sample.com/api Preview data request: https://cms.sample.com/api?token=foo

What we were doing:

public, cached data

const { data } = await getClient().query({ query, variables });

private, uncached draft data

const { data } = await getClient(token).query({ query, variables });
export const { getClient } = registerApolloClient(token => {
  // add the token to the uri in case we're getting preview data
  const uri = token
    ? `${process.env.CMS_API_URL}?token=${token}`
    : process.env.CMS_API_URL;

  return new ApolloClient({
    cache: new InMemoryCache({
      possibleTypes,
    }),
    link: new HttpLink({
      uri,
      headers: {
        Authorization: `Bearer ${process.env.CMS_AUTH_TOKEN}`,
      },
    }),
  });
});

export default getClient;
phryneas commented 1 month ago

It's probably better in an issue, since you'll not be the last person with this question :)

So I'll write a bit more of a "general answer here":

Modifying a request URI as a "one-off" with an existing Apollo Client

Generally, there are a few different ways you can modify the uri of outgoing requests.

From call site:

You can just use the uri value in context with an outgoing query

const { data } = await getClient().query(MY_QUERY, {
  context: {
    uri: "someOtherUri",
  },
  fetchPolicy: "no-cache",
});

Using an uri callback

In an uri callback you have access to the full operation, which means that you could look at things like operation.operationName or operation.variables - or in this example, operation.getContext().

export const { getClient } = registerApolloClient(() => {
  return new ApolloClient({
    cache: new InMemoryCache({
      // ...
    }),
    link: new HttpLink({
      uri(operation) {
        const context = operation.getContext();
        if (context.token) {
          return `${process.env.CMS_API_URL}?token=${context.token}`;
        }
        return process.env.CMS_API_URL;
      },
      // ...
    }),
  });
});

In your case, you'd then call that with token in the context - your call site wouldn't really be aware of the uri abstraction:

const { data } = await getClient().query(MY_QUERY, {
  context: {
    token: "myToken",
  },
  fetchPolicy: "no-cache",
});

❗Note that in both of these examples, I used fetchPolicy: "no-cache".

That's not generally necessary, but something to consider: You are redirecting your Apollo Client to a different data source here. If you wouldn't use no-cache, the results from that other data source would get merged into your existing InMemoryCache - and in your case, where you probably have "original" and "draft" with the same typename and id, there's a risk that the drafts would overwrite the originals in the cache.

Creating more than one cache

With that warning note from the last example - you might also want to consider creating more than one cache.

In that case, call registerApolloClient twice to intentionally create up to two independent cache instances per incoming request (they will be created once you call getClient respectively).

const { getClient } = registerApolloClient(...)
// this will probably still need something like context to pass in the token for this cache on this request., unless you get the token from a cookie, then you can directly call `cookies()` inside the callback function in `registerApolloClient`.
const drafts = registerApolloClient(...)

later distinguish between getClient() calls and drafts.getClient() calls.

reilnuud commented 1 month ago

Awesome -- thanks! This is super helpful.

phryneas commented 1 month ago

Great! I'm gonna close this issue then. Have a great day!

github-actions[bot] commented 1 month ago

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.