skip to content
Samuel Edwin's Website

GraphQL - How Do Nested Queries Work?

/ 4 min read

In this post we’ll learn how GraphQL process nested queries and fetch all the requested data from the server.

One advantage of GraphQL is we can get the requested data as much as we need.

For an example, let’s say we want to show the details of a product, this query should do the job:

query.graphql
query ProductDetail {
product(id: "1") {
id
name
price
description
}
}

Which will return a following response:

response.json
{
"data": {
"product": {
"id": "1",
"name": "phone",
"price": 100,
"description": "This is a phone"
}
}
}

If we also need to display the shop’s information for that product, we can add a nested query:

query.graphql
query ProductDetail {
product(id: "1") {
id
name
price
description
shop {
id
name
image
}
}
}

GraphQL will happily get the shop information for the product.

response.json
{
"data": {
"product": {
"id": "1",
"name": "phone",
"price": 100,
"description": "This is a phone",
"shop": {
"id": "a",
"name": "Shop A",
"image": "[shop A image]"
}
},
}
}

If we want to go one step further, like getting the shop’s owner information, we can add more query:

query.graphql
query ProductDetail {
product(id: "1") {
id
name
price
description
shop {
id
name
image
user {
id
name
}
}
}
}

Which will return a following response:

response.json
{
"data": {
"product": {
"id": "1",
"name": "phone",
"price": 100,
"description": "This is a phone",
"shop": {
"id": "a",
"name": "Shop A",
"image": "[shop A image]",
"user": {
"id": "user1",
"name": "John Doe"
}
}
},
}
}

How does it all work under the hood? We’ll find out very soon.

Introducing resolvers

The resolver is the core machine that makes GraphQL work.

It is close to a REST API endpoint that takes a request and returns a response.

But the resolver has a big difference compared to REST API endpoint.

The resolver knows when to fetch more data, or stop and send the response back to the client.

Assuming we have a following schema:

schema.graphql
type Query {
product(id: ID!): Product
}
type Product {
id: ID!
name: String!
description: String!
price: Float!
shop: Shop!
}
type Shop {
id: ID!
name: String!
image: String!
user: User!
}
type User {
id: ID!
name: String!
}

Here’s how the resolver code looks like using Apollo Server:

resolver.js
const resolvers = {
Query: {
product: (parent, { id }) => {
return db.products.find(id)
}
},
Product: {
shop: (product) => {
return db.shops.find(product.shopId)
}
},
Shop: {
user: (shop) => {
return db.users.find(shop.userId)
}
}
}

How resolvers process query

Now let’s see how the resolver works when we run the query.

We’ll use the same query above:

query.graphql
query ProductDetail {
product(id: "1") {
id
name
price
description
shop {
id
name
image
user {
id
name
}
}
}
}

Here’s what going to happen to our GraphQL server:

  1. Query resolver product is called with id “1”, fetch the product with id “1” from the database.
Query: {
// ignore the parent field
// the id is "1", passed from the top level query
product: (parent, { id }) => {
return db.products.find(id)
}
},
  1. The Product.shop resolver is called along with the product data we received from Query.product.
Product: {
// product is the data we received from Query.product
shop: (product) => {
// fetch the shop data with the shopId from the product
return db.shops.find(product.shopId)
}
},
  1. The Shop.user resolver is called along with the shop data we received from Product.shop.
Shop: {
// shop is the data we received from Product.shop
user: (shop) => {
// fetch the user data with the userId from the shop
return db.users.find(shop.userId)
}
}

Once the resolver knows we have all the data we need, it will send the response back to the client.

Here’s a diagram for you visual learners, (Ps: Please zoom it on your browser to see it clearly, my website doesn’t have an image viewer 😂) GraphQL resolver state diagram

Conclusion

GraphQL has an awesome capability to fetch all the related data we need with a single query.

In order to do so, it uses resolvers to decide whether to fetch more data or send it back to the client.

The resolvers receive two things on each call:

  1. The parent object we received from the parent resolver.
  2. The arguments we passed from the query.

That’s all. I hope you enjoy this post.