I'm working on a GraphQL API which sits in front of a REST service. This REST service is a single endpoint with a lot of complex parameters - rather than put all the parameters on a single GraphQL field, we've logically broken it down into a tree of GraphQL fields and types.
In particular, the bottom of this tree is somewhat dynamic and unbounded. All in, it looks something like this simplified schema:
type Query {
outer: OuterWrapper!
}
type OuterWrapper {
inner: InnerWrapper!
}
type InnerWrapper {
recurse(name: String = ""): RecursiveType!
}
type RecursiveType {
recurse(name: String = ""): [RecursiveType!]!
name: String!
}
Currently, we have just one Apollo resolver at the top of the tree (outer
), and use the graphql-fields
library to process the info
parameter. Essentially, we're 'looking ahead' to the children - a popular pattern to optimise backend queries.
This works quite well for us - we map the child fields and parameters into a single REST request, and then map the response back into the correct structure.
However, it does have two limitations:
- The
graphql-fields
response doesn't include the values of default parameters. If I wrote a properrecurse
resolver, Apollo would pass in the schema default value forname
if it wasn't in the query. I found an alternative library (graphql-parse-resolve-info
) that I'm going to switch to, but that's not an Apollo solution, and... - If we throw an error, the path reflects that it occurred in the
outer
resolver, rather than further down the tree where it would be more accurate, and useful to the user.
Taken together, I'm concerned that I'm going to continue finding things that don't quite work using this kind of structure, and I'm thinking about how I could move away from it.
Is there a way I could incrementally build my single backend REST request using a traditional/fully specified resolver structure?
I could imagine the resolvers building up the query, and storing it in the context
- the outer
resolver would create the query, and subsequent resolvers would change it as they see fit. Then each resolver could return a promise that's waiting on the REST response. However, I can't see a good way to know when all my resolvers have been called (and made their changes), and thus to fire the REST request to the backend, or for each resolver to know where it sits in the query structure (and hence where to look for data in the REST response).
Is there another approach I haven't considered?