I didn't know how to title this question but here's what I am uncertain about.
I have a React frontend that is making a GraphQl query to our GraphQl midlayer which aggregates data by making calls to our legacy REST api.
So for example in React I can call the getCustomer
query:
query getCustomer($id: Int!) {
getCustomer(id: $id) {
name
email
}
}
which will hit the getCustomer
resolver which then makes a request to our REST customers/{id}
endpoint to return our data.
async function getCustomer(_, { id }, ctx) {
const customer = await ctx.models.customer.getCustomer(id);
return customer;
}
This request is fine if I am printing a list of customers. But where my questions comes into play is how can I make conditional API requests in my resolver based on data I am querying?
Say each customer can have multiple addresses and these addresses live on a different endpoint. I would love to get those addresses like this in my frontend:
query getCustomer($id: Int!) {
getCustomer(id: $id) {
name
email
address {
city
}
}
}
How could I have my resolver handle this based on my types
and schemas
? Something fundamentally like this:
async function getCustomer(_, { id }, ctx) {
const customer = await ctx.models.customer.getCustomer(id);
[If the query includes the address field]
const addresses = await ctx.models.customer.getAddressesByCustomer(id);
customer.addresses = addresses;
[/If]
return customer;
}
Ultimately, the goal is to have the getCustomer
resolver be capable of returning all customer data across various endpoints based on what fields are sent in the query but not making those additional API requests if the field isn't requested.
There are effectively two ways to do this. The first relies on how GraphQL executes requests. A field's resolver will only be called if 1) the "parent" field is not null and 2) the field in question is actually requested. This means we can provide a resolver for the address field explicitly:
const resolvers = {
Customer: {
addresses: () => {
return ctx.models.customer.getAddressesByCustomer(id)
},
},
}
In this way, the resolver will be called for a query like
{
query getCustomer($id: Int!) {
getCustomer(id: $id) {
name
address {
city
}
}
}
}
but will not be called for
{
query getCustomer($id: Int!) {
getCustomer(id: $id) {
name
}
}
}
This approach works well enough when wrapping a simple REST API. Some REST APIs, however, allow you to request related resources through optional parameters. Similarly, if you're pulling the data out of a database, you can join additional tables to your query. The end result is more data in fewer roundtrips. In this case, you would do all the fetching at the root level (inside the getCustomer
resolver). However, you would want to determine what fields the custom actually requested. To do this, you would parse the resolve info object, which is the fourth parameter passed to every resolver. Once you determine which fields were actually requested, you can make the appropriate changes to your URL or SQL query.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With