skip to Main Content

I have two microservices ms1 and ms2 that output different types of data each time an API gateway built with Apollo GraphQL sends requests through resolvers.

To be specific, the first resolver getCart(userId: ID!): Cart! calls ms1 and it responds it with the following response body:

{
  "items": [
    {
      "itemId": "8a016af8-5336-4799-b806-73976996aa6f",
      "quantity": 1
    },
    {
      "itemId": "9a016af8-5336-4799-b806-73976996aa6f",
      "quantity": 2
    }
  ]
}

That is, the Cart type, just an object containing an array of items with id and quantity.

The second resolver getItem(itemId: ID!): Item! calls ms2 and it responds it with the following response body:

{
  "itemId": "7a016af8-5336-4b99-b806-73976996aadf",
  "name": "Jack Daniel's Blue Label",
  "price": 29.99
}

Is there any way to "combine" or sequence these two queries directly from a frontend (e.g. Apollo Sandbox or GraphiQL) in such a way that I get the following response?

{
  "items": [
    {
      "itemId": "8a016af8-5336-4799-b806-73976996aa6f",
      "name": "Jack Daniel's - Honey",
      "price": 19.99,
      "quantity": 1
    },
    {
      "itemId": "7a016af8-5336-4799-b806-73976996aa6f",
      "name": "Jack Daniel's - Red Label",
      "price": 29.99,
      "quantity": 2
    }
  ]
}

In pseudo-graphql I was thinking about something like this:

query GetCartInfo($userId: ID!) { 
  getCart(userId: $userId) {
    items {
      itemId => getItem(itemId: $itemId) {
        name
        price
      }
      quantity
    }
  }
}

If that’s not possible, what would be the best way to achieve this? Create a new resolver in the API gateway? Modify the array from another service in the frontend?

Thanks for the help and information about the topic, I would really appreciate it 😀

Have a nice day!

3

Answers


  1. You can possibly use GraphQL’s field resolvers, which allow defining custom logic to resolve fields in a GraphQL query.

    In the API gateway, you can create a new resolver that resolves the items field in the getCart query. Here is a rough example:

    const resolvers = {
      Query: {
        getCartInfo: async (_, { userId }) => {
          // Send a request to ms1 to get the cart data
          const cartData = await ms1API.getCart(userId);
    
          // Send a request to ms2 to get item details For each item
          const itemPromises = cartData.items.map(async (item) => {
            const itemDetails = await ms2API.getItem(item.itemId);
            return {
              itemId: item.itemId,
              name: itemDetails.name,
              price: itemDetails.price,
              quantity: item.quantity,
            };
          });
    
          const items = await Promise.all(itemPromises);
    
          return {
            items,
          };
        },
      },
    };
    

    I hope this will help.

    Login or Signup to reply.
  2. The best way would be to do this not on the client, but on the server by using a Gateway or Federation Router service in front of your microservices that stitches both of these Item entities into one and answers your query for Item with only one query from the client, but answers from both microservices.

    One way of doing that would be Apollo Federation. You can read up on that here.

    Login or Signup to reply.
  3. You can’t do this from the client, you need to modify the GraphQL schema and your resolvers.

    In your schema you should have a Cart type and an Item type and also a CartItem type to deal with quantities.

    type Cart {
      id: ID!
      contents: [CartItem]
    }
    
    type Item {
      id: ID!
      name: String!
      price: Float!
    }
    
    type CartItem {
      id: ID!
      quantity: Int!
      item: Item!
    }
    

    Given this structure, your query:

    getCart(userId: ID!): Cart!
    

    Should be able to be called with:

    query getUsersCart($userId: ID!) {
      getCart(userId: $userId) {
        cartItem {
          quantity
          item {
            id
            name
            price
          }
        }
      }
    }
    

    Your getCart query resolver needs to invokes ms1 to get the the list of CartItems and then, like @orabis suggests, your CartItem type needs a resolver for the item field that invokes ms2. GraphQL will automatically combine the results of the query resolver with the field resolver.

    The response should look like:

    [
      "cartItem": {
        "quantity": 1,
        item: {
          "id": "8a016af8-5336-4799-b806-73976996aa6f",
          "name": "Jack Daniel's - Honey",
          "price": 19.99
        }
      },
      cartItem: {
        "quantity": 2,
        item: {
          "id": "7a016af8-5336-4799-b806-73976996aa6f",
          "name": "Jack Daniel's - Red Label",
          "price": 29.99
        }
      }
    ]
    

    I should add that if this query is to be done by the user who owns the cart then for security the user’s id should not be a parameter to the query. The user’s id should be provided by the context parameter to the query resolver.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search