December 13, 2022

Personalizing the e-commerce shopping experience with GraphQL

Shane Myrick

Shane Myrick

This post is a part of our “How to power modern retail apps with Apollo GraphOS” series. Also in this series:


Only 42% of e-commerce stores provide a proper mix of alternative and supplementary product recommendations (Baymard Institute). Implementing these product suggestions ultimately requires upgrades to both the backend services that can recommend related products based on metadata or even machine learning models as well as the frontend apps that display the data.

However, requiring teams to upgrade to a new API version or make separate calls to a new recommendation service can slow down the speed at which they can roll these features out in customer-facing apps. Luckily, GraphQL can help with this.

Making product recommendations available

There are many ways you can provide personalized data but the most common is including a list of recommended products on a product page. In a REST-based microservice architecture, you will most likely have some service that takes in a product and user ID and returns a list of suggested product IDs.

It is then usually up to the client app to call the product service again for each recommendation in order to get those products’ details and prices. This makes sense when your primary goal is to maintain separation of concerns in your microservices—you don’t want to duplicate basic product details in the recommendation service because that service should instead be focused on providing the best suggestions possible.

However, with REST-based approach, each client app would need to duplicate this multi-request business logic and you may end up with the infamous “N+1 problem” where each call to resolve recommended product data is a unique call from each client. This is where Apollo Federation can step in and help solve our issues. Using the federation directives we can link our services together in a new discovery subgraph:

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])


type User @key(fields: "id") {
  id: ID!

  """
  Suggest products for this user
  """
  recommendedProducts(productId: ID = null): [Product]
}

type Product @key(fields: "id") {
  id: ID!

  """
  Related products for this product
  """
  recommendedProducts: [Product]
}

These type definitions in the new discovery schema may look minor, but let’s dive deeper to understand what’s going on here.

Federation 2 type linking

The top of the schema is defining which directive from the Federation spec we would like to use

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])

Next, we are adding a new field to the User type to get a list of recommended products. This field will be added to any location where a User is returned. That means that we can use the user id to provide more personalized results and we can provide product suggestions on any page we are fetching the user. In the cases where we want to also provide suggestions based on the current product the user is looking at, we will need to allow clients to optionally pass in the product id.

type User @key(fields: "id") {
  id: ID!

  """
  Suggest products for this user
  """
  recommendedProducts(productId: ID = null): [Product]
}

Finally, we will provide another way to get to the same data using the Product type. This is helpful for the queries where you are not selecting the User directly, (such as on the product details page) but still want to provide suggestions. We could have an optional argument here for the user id to support the same user-based suggestions but our user id is actually already passed to the server through the x-user-id header, so there is no need to include it in the schema.

type Product @key(fields: "id") {
  id: ID!

  """
  Related products for this product
  """
  recommendedProducts: [Product]
}

Linking to Other Subgraphs

We have also just solved the N+1 problem for free without taking any additional steps. Apollo Federation will take care of figuring out which field is resolved from which subgraph, so when our clients want to make queries that fetch all the suggested products and the price of each item the query planner will optimize to use the fewest requests possible.

What’s next?

With our schema in place, we can now focus our attention on optimizing the recommendation engine and evolving how we recommend products to our users without ever breaking our existing use cases.

We could add new optional arguments that allow clients to select what type of recommendations they want, like recently viewed or new deals, but the schema can still stay the same. We might even consider making that decision for clients based on who is calling or who the user is so all our business logic lives on the server.

Get started with a retail supergraph today

Beyond powering better product recommendations, the best way to see the possibilities of a supergraph is to try one out. You can explore a retail supergraph schema and run real queries against it here.

We also have additional posts in this series of retail best practices that dive into different elements of this schema to illustrate how Apollo GraphOS help power essential features of modern retail applications.

If you’d like to talk to an Apollo expert about how a supergraph can power your retail experience, please reach out to us.

Written by

Shane Myrick

Shane Myrick

Read more by Shane Myrick