Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GraphQl and Conditional Resolver API Requests

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.

like image 516
Yuschick Avatar asked Sep 06 '25 04:09

Yuschick


1 Answers

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.

like image 78
Daniel Rearden Avatar answered Sep 10 '25 01:09

Daniel Rearden