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-fieldsresponse doesn't include the values of default parameters. If I wrote a properrecurseresolver, Apollo would pass in the schema default value fornameif 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
outerresolver, 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?