7
votes

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

1
How are you building your schema? Are you calling the GraphQLSchema constructor directly or using a utility function like buildSchema or makeExecutableSchema? Or is the schema creation coupled with some other library you're using, like Apollo Server?Daniel Rearden
I am using makeExecutableSchema via graphql-tools and serving it via Apollo Server.Magnum

1 Answers

5
votes

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.