Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can GraphQL optionally resolve a field given result of a query in resolver?

I have the following REST endpoints:

/orders/{id}

returns {
    orderId,
    orderItem,
    customerId
}

/customers/{id}

returns {
   customerId,
   firstName,
   lastName
}

I am limited by these two endpoints, which are going to be wrapped in my graphql schema.

I would like the following Schema:

type Order {
  orderId: ID!,
  orderItem: String,
  customer: Customer
}

type Customer{
  customerId: ID!
  firstName: String!
  lastName: String!
}

type Query {
  getOrder(id: String!): Order,
  getCustomer(id: String!): Customer
}

I'm wondering if it is possible to have GraphQL resolve the Customer object in the Order type? I understand that you cannot pass the result of a query into the parameter of another.

I have considered the resolver of getOrder be:

const getOrderResolver = axios.get(`/orders/${id}`)
  .then((ordersRes) => {
    let customerId;
    if(ordersRes.data.customerId !== null) {
      customerId = ordersRes.data.customerId 
      axios.get(`/customers/${customerId}`)
      .then((customerRes) => ({
        return {
          orderId: ordersRes.data.orderId
          orderItem: ordersRes.data.orderItem
          customer: {
            customerId: customerRes.data.customerId
            firstName: customerRes.data.firstName
            lastName: customerRes.data.lastName 
          }
        }
      })
    } else {
      return {
          orderId: ordersRes.data.orderId
          orderItem: ordersRes.data.orderItem
          customer: null
      }
    }
    })
  })

getCustomer resolver

const getCustomerResolver = axios.get(`/customers/${customerId}`)
          .then((customerRes) => ({
            return {
                customerId: customerRes.data.customerId
                firstName: customerRes.data.firstName
                lastName: customerRes.data.lastName 
            }
          })

It seems with my solution, there will be the additional cost of always fetching the Customer type whether or not it is queried within the getOrder query. Is it possible to rewrite my GraphQL schema in a way that GraphQL would be able to resolve the Customer type only when queried?

The limitation of my given my ORDERS REST API only returns the CustomerId makes it difficult to resolve in getOrder, since the Customer API requires a customerId

like image 286
Magnum Avatar asked May 16 '19 14:05

Magnum


People also ask

How does GraphQL resolve query?

Each field on each type is backed by a function called the resolver which is provided by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value. If a field produces a scalar value like a string or number, then the execution completes.

Can GraphQL return an object?

In many cases, you don't want to return a number or a string from an API. You want to return an object that has its own complex behavior. GraphQL is a perfect fit for this.

What does a GraphQL client usually do before caching the results of a query?

Generally, when caching data, the intuition is to put information that's fetched remotely into a local store from where it can be retrieved later on. With GraphQL, the naive approach would be to simply put the results of GraphQL queries into the store and simply return them whenever the same query is sent.


1 Answers

There's two things to keep in mind here:

  • GraphQL won't resolve a field unless it's requested (i.e. your resolver will not be called unless the field is actually included in the request).
  • Each resolver has access to the value its "parent" field resolved to.

So your getOrder resolver can only worry about returning an order object:

const resolvers = {
  Query: {
    getOrder: (parent, args, context, info) => {
      const response = await axios.get(`/orders/${args.id}`)
      return response.data
    },
    ...
  },
  ...
}

Note that if the REST endpoint returns a response in the same "shape" as your field's type, there's no need to map the response data to your actual fields here -- just return response.data.

Now we can add a resolver for the customer field on our Order type:

const resolvers = {
  Query: { ... },
  Order: {
    customer: (parent, args, context, info) => {
      if (!parent.customerId) {
        return null
      }
      const response = await axios.get('/customers/${parent.customerId}')
      return response.data
    },
  },
  ...
}

Our parent field here will be getOrder (or any other field that has an Order type). The value of parent will be whatever value you returned in that field's resolver (or if you returned a Promise, whatever that Promise resolved to). So we can use parent.customerId for the call to the /customers endpoint and return the data from the response. And again, this resolver will only be called if the customer field is actually requested.

like image 94
Daniel Rearden Avatar answered Sep 30 '22 19:09

Daniel Rearden