6

I have the following query:

const getPage = gql`
    query Page($path: String!) {
        page(path: $path) @rest(type: "Page", path: "{args.path}") {
            blocks @type(name: Block) {
                name
                posts @type(name: Post) {
                    body
                    author
                }
            }
            authors @type(name: Author) {
                name
            }
        }
    }

In blocks.posts.author there's only an AuthorId. The authors object is containing all the available authors.

I'd like to replace/match the AuthorId with it's corresponding object. Is it possible to do this within one query?

I also wouldn't mind to have a separate query for Author only (fetch will be cached, no new request would be made), but I still don't know how would I match it through 2 queries.

Example API response

{
   blocks: [
      {
         posts: [
             {
                id: 1,
                title: 'My post',
                author: 12,
             }
         ]
      }
   ],
   authors: [
      {
         id: 12,
         name: 'John Doe'
      }
   ]
}

What I want with 1 query that author inside a post becomes the full author object.

wintercounter
  • 7,500
  • 6
  • 32
  • 46
  • Based on the directives, it looks like you're using `apollo-client` and `apollo-link-rest`, but this is not mentioned in your question or reflected by the tags. – Daniel Rearden Jul 23 '19 at 13:10
  • Thanks, added to it. – wintercounter Jul 23 '19 at 13:14
  • I added an answer that should work, although it's been a bit since I've played around with that link. It's a neat lib if you have a very basic REST endpoint you're wrapping but you should consider setting up a separate GraphQL server as a gateway instead for more complicated cases. This gives you more flexibility in how to resolve fields and opens the way for caching the REST responses and other features that just aren't possible with apollo-link-rest. – Daniel Rearden Jul 31 '19 at 12:37
  • Caching will be solved with SW on browser level, gives us more control. We tried to have such solution what you posted but what you have there requires a separate endpoint as I can see which we don't have. We cannot change this, it's a PHP API that works like this, we have to deal with that :) – wintercounter Aug 01 '19 at 11:53

2 Answers2

0

Great question. With GraphQL, you have the power to expand any field and select the exact subfields you want from it, so if you were using GraphQL on your backend as well this would be a non-issue. There are some workarounds you can do here:

If all of the Author objects are in your Apollo cache and you have access to each Author's id, you could use ApolloClient.readFragment to access other properties, like this:

const authorId = ...; // the id of the author

const authorInfo = client.readFragment({
  id: authorId,
  fragment: gql`
    fragment AuthorInfo on Author {
      id
      name
      # anything else you want here
    }
  `,
});

Although it's worth noting that with your original query in the question, if you have all of the Author objects as a property of the query, you could just use Javascript operations to go from Author id to object.

const authorId = ...; // the id of the author
data.page.authors.find(author => author.id === authorId);
  • Thanks for the detailed answer, however this isn't helping to me. Your first suggestion assumes I already have the ID which I don't, everything is in the same response. The second is what we were doing and wanted to avoid as it was causing certain issues. – wintercounter Jul 29 '19 at 06:06
  • @wintercounter What are the type definitions of `Post` and `Author`? Based on your question I assumed `Post.author` is some unique reference to an author, either by name or id. You can configure the Apollo cache to use whatever field (or a combination of fields) as a unique identifier, so the first approach should still be valid. More info here: https://www.apollographql.com/docs/react/advanced/caching/#normalization – Jason Paulos Jul 30 '19 at 22:05
  • Yes, you got that right but `authors` is also coming in the same API response. So the response is basically `{posts: [], authors: []}`, so I don't have the author ID before. – wintercounter Jul 31 '19 at 12:04
  • Added example API response. – wintercounter Jul 31 '19 at 12:08
0

The following should work.

First, capture the author id as a variable using the @export directive. Then add a new field with some name other than author and decorate it with the @rest, using the exported variable inside the path.

So the query would look something like this:

query Page($path: String!) {
    page(path: $path) @rest(type: "Page", path: "{args.path}") {
        blocks @type(name: Block) {
            name
            posts @type(name: Post) {
                body
                author @export(as: "authorId")
                authorFull @rest(
                    path: '/authors/{exportVariables.authorId}'
                    type: 'Author'
                ) {
                    name
                }
            }
        }
        authors @type(name: Author) {
            name
        }
    }
}

You can use the fieldNameNormalizer option to rename the author property in the response to a field with a different name (for example, authorId). Ideally, that should still work with the above so you can avoid having a weird field name like authorFull but apollo-link-rest is a bit wonky so no promises.

Daniel Rearden
  • 80,636
  • 11
  • 185
  • 183