Apollo-link-rest: How to access response.headers with apollo-link-rest ?

Created on 11 Nov 2020  ·  6Comments  ·  Source: apollographql/apollo-link-rest

Hi all,

I wat to use apollo-link-rest to access a rest api, however, I can not per useQuery() access the response.headers.

For example, a rest api with pagination, which put the first, prev, next, last information in the header. How can I access that?

I made an example here: https://codesandbox.io/embed/how-to-access-response-0mqx7?file=/src/Client.ts

You can go to the develop tools Network tab, see the response with Headers,in link, there is the pagination information. But I have no idea, how to access the Headers....

Hier is my main code, I have no idea, how to get the response.headers.link...

  const queryTodos = gql`
      query Todos($page: number, $limit: number) {
        todos(_page: $page, _limit: $limit)
          @rest(type: "[Todo]", path: "/todos/?{args}") {
            userId
            id
            title
            completed
        }
     }
   `;

export const Todos = () => {
  const { data, loading, error } = useQuery(queryTodos, {
    variables: { page: 1, limit: 3 },
    fetchPolicy: "no-cache"
  });

 ...

  return (<div>...</div>)

I also asked this question in stackoverflow: https://stackoverflow.com/q/64791676/3279996 , in case you are appreciate to have more points in stackoverflow. :D

enhancement enhancement💡 feature help wanted 🛠 question❔

All 6 comments

@blatoo -- unfortunately, that is a feature that doesn't currently exist in apollo-link-rest.

Fundamentally, GraphQL does not expect to deal with Headers -- because those would have to be returnable deep in the graph.

If somebody wanted to implement this, I could see us adding __headers as a secret/automatic field in the "body" of a response, but we'd have to add a feature-flag to enable this behavior, and do some mangling to the header keys so that they're graphql compatible.

const queryTodos = gql`
      query Todos($page: number, $limit: number) {
        todos(_page: $page, _limit: $limit)
          @rest(type: "[Todo]", path: "/todos/?{args}") {
+             __headers {
+                         XMyHeader
+             }
            userId
            id
            title
            completed
        }
     }
   `;

@fbartho thanks so much for this answer. It would be a great feature, if we can access the header information. I use json-server, they put the pagination informations in the Headers, also for the cursor based pagination infos.

By the way, I saw in your documentation there are : responseTansformer and customFetch options. Can I do something with them to get the pagination information??? Actually I tried a lot, but still no success...:

https://www.apollographql.com/docs/link/links/rest/#response-transforming

https://www.apollographql.com/docs/link/links/rest/#custom-fetch

Unfortunately, apollo-link-rest relies on apollo-link's API to plug into GraphQL -- but this means that I don't think there's an obvious way to share the JS Headers API directly -- but a denormalized version of it might be possible.

That said, the responseTransformer or the customFetch options could be used to patch what you need.

The responseTransformer receives the JS Response object which has a response.headers attribute.

So you could write something like this:

responseTransformer: async (resp: Response) => {
    const body = await resp.json();
    const interestingHeaders = {};
    interestingHeaders.headerOne = resp.headers.get("headerOne"); // Pluck out the headers you want
    body.__headers = interestingHeaders
    return body;
}

Depending on your browser version you may be able to use headers.entries() to get all of the headers as one object instead of headers.get() -- but that's not currently available in Safari-iOS.

This is obviously simplified, and error handling is left to you, but I think this would work!

customFetch could also do it, but I think responseTransformer is easier.

@fbartho Wow, this is cool! I just tried your code, it works and I got the link.

Now it comes another problem, how can I access the body.__headers.link in query?

my query code is:

const queryTodos = gql`
  query Todos($page: number, $limit: number) {
    todos(_page: $page, _limit: $limit)
      @rest(type: "TodoPagination", path: "/todos/?{args}") {
      __headers {
        link
      }
      userId
      id
      title
      completed
    }
  }
`;

But what the value of the __headers is null...
The code is here: I added your code in Client.ts and the Query code is in Todo.tsx:
https://codesandbox.io/s/how-to-access-response-0mqx7?file=/src/Todos.tsx

@fbartho Hi, I tried three different way, at last, I solved this problem finally. However, I still doubt, whether it is a good solution. Can you give me a your opinion?

Methode 1: failed.
At first, I think, I can query the __headers in useQuery(), tried multiple times, but failed.

Method 2: didn't tried, because I doubt the solution
change the structure of the response in responseTransformer, put the __headers in a __headers key and also put the todo list in a todo key. However, I think, it is not a good solution. Because, all the structure of the response will be changed. So I have to rewrite all the structure in useQuery().

Method 3: success and dead simple
I put the __headers in a reactive variable. In responseTransformer function, I just put the value of __headers in a reactive variable, and query it after the useQuery() executed. However, I am still not sure, because, I have to make a different reactive variable for different query...

@blatoo , this is an older issue, which I my self just came across, solved it with a new ApolloLink

const paginationLink = new ApolloLink((operation, forward) => {
  return forward(operation).map((response) => {
    const context = operation.getContext();

    const { headers } = context.restResponses[0] || null;
    // in my case i'm making a bunch of sub queries, so i'm using `[0]` to get the headers from the top level.

    if (headers) {
      const pagination = getPaginationFromHeaders(headers.get('link'));

      return { ...response, data: { ...response.data, pagination } };
    }
    return response;
  });
});

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: concat(paginationLink, restLink),
});
Was this page helpful?
0 / 5 - 0 ratings