Search code examples
graphqlgraphql-jsgraphql-java

Multiple item lookup in GraphQL Query


I'm wondering what the most common approach to looking up multiple items based on ID is. From my naive understanding, I see 3 options:

Option 1

Accept array of IDs and return all results

products(ids: [ID!]!): [Product!]!

This seems to be the most straightforward and intuitive approach, but doesn't allow clients to index into the results and strays from the pagination pattern other queries typically follow.

Option 2

Force clients to use aliases

product(id: ID!): Product!

This is easiest to implement on the server side and also allows clients to index directly into the results based on the ID (assuming they use the ID as the alias), but also forces clients to construct more complex queries.

Option 3

Accept array of IDs and return paginated results (via the Connection pattern)

products(ids: [ID!]!, after: String, first: Int! = 10): ProductConnection!

This stays consistent with other queries that return paginated results, but also has the same issue as #1 with not allowing clients to index directly into the results via the ID (assuming ID is used as the alias as in option 2). The connection pattern is also not immediately intuitive for users not familiar with it.

Any suggestions based on your experiences? Thanks!


Solution

  • This answer is not neccesarily introducing more ways of doing things but I will elaborate some best practices that I have found for my schemas and how they relate to projects in the community.

    Option 2 is not viable with static queries

    As I have already discussed in the comment think about what your app really needs. If you want to lookup a dynamic number of fields by ID Option 2 is not going to be helpful when you want to use static queries. You would have to be concatinating strings to create dynamic queries roughly like so:

    const query = `{ ${ids.map(id => `product${id}: product { ...Frag }\n`).join('')} }`;
    

    This is a bad idea and the linked article talks about why and also your GraphQL endpoint is probably not made for it. We could as well send multiple queries one by one for each product but then we lose one of GraphQLs key advantages over REST.

    But: We usually keep the field for querying single entities because viewing a single item is usually a use case of our app and we can very quickly respond to that query. See the last section of my answer on queries.

    The other two options are the same basically

    Option 1 and Option 3 should just be chosen consistently across your project. The connection might not really be helpful when querying for IDs but it can prevent returning way to many items and seamlessly integrate with Relay.

    You also need to consider how you then connect the IDs on the frontend with the values again. Do you return the items in the same order as queried (as know for example from Dataloader) or do you query the IDs of the entities and the order does not matter.

    So if all your queries that return multiple items return connections then I would do it here as well. The danger is that queries will get to complex and this leads me to the last topic.

    Generic filters considered harmful

    In general it is recommended to only expose queries that are really required by your frontend(s) as this Twitter thread elaborates and Lee Byron (one of the creators) adds that you should also only expose queries that you can fulfill efficiently. In contrast Apollo recommends to not couple clients and server implementations and instead but abstract schemas. I think you will have to find a balance.

    I think that exposing a filter object that is very specific is almost as good as creating a new field each time but it might not scale that well. You will quickly need to build complex queries and a query builder library might be helpful. On the other hand if you already have that in place - like in your example products might already be heavily filterable - it can be very easy to add.

    Some project in the wild offer extensive filters like Hasura, Prisma and Gatsby. So if you want to build more generic filters you could take inspiration from these projects.