Skip to main content
GraphQL Basics
CHAPTER 11 Beginner

Nested Queries and Relationships

Updated: May 13, 2026
25 min read

# CHAPTER 11

Nested Queries and Relationships

1. Introduction

The "Graph" in GraphQL refers to how data is connected. In the real world, data is rarely flat. Users have profiles, profiles have posts, and posts have comments. In this chapter, we will learn how GraphQL handles these parent-child relationships. We will explore how to structure deeply nested queries, how the server resolves these relational queries, and the common pitfalls (like the N+1 problem) to avoid when working with relational databases.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Write nested GraphQL queries to fetch relational data.
  • Understand how the $root argument links parent data to child resolvers.
  • Map GraphQL relationships to SQL JOINs or secondary queries.
  • Identify the N+1 performance problem in relational queries.

3. Beginner-Friendly Explanation

Imagine a family tree. You are the "Root" of the tree. If someone asks for your details, they get your name. But what if they also want your parents' details? They "nest" a request. They look at you (the $root), find out who your parents are, and fetch their details.

In GraphQL, when you query a Post, the Post is the parent. If you also ask for the Author inside the Post, the server fetches the Post first. Then, it takes the author_id attached to that Post, and passes it to the Author resolver. The Author resolver acts like a detective, using that ID to find the correct Author in the database and returning it inside the nested structure.

4. Real-World Examples

  • Music App: A query for an Album contains an array of Tracks. Each track contains a Genre and an Artist. The data is deeply relational but can be fetched in one request.
  • E-Commerce: An Order contains Items. Each item is linked to a Product. The product has a Category. A single GraphQL query can traverse this entire path: Order -> Item -> Product -> Category.

5. Detailed Code Examples

Let's look at the Schema and Query for a Blog application.

The Schema:

graphql
1234567891011121314151617
type Author {
  id: ID!
  name: String!
  email: String!
}

type Post {
  id: ID!
  title: String!
  content: String!
  # This establishes the relationship
  author: Author! 
}

type Query {
  getPost(id: ID!): Post
}

The Nested Query:

graphql
12345678910
query FetchPostWithAuthor {
  getPost(id: "101") {
    title
    content
    author {
      name
      email
    }
  }
}

6. How Resolvers Handle Relationships

When the query above executes, how does the server know who the author is? It uses the $root argument!

PHP Resolver Logic:

php
123456789101112131415
<?php
// Inside the Post Type Definition
&#039;author' => [
    &#039;type' => $authorType,
    &#039;resolve' => function ($root, $args, $context) {
        // $root contains the Post data (e.g., ['id' => 101, 'author_id' => 5, 'title' => '...'])
        $authorId = $root[&#039;author_id'];
        
        // Fetch the author from the database using the ID found in the parent Post
        $sql = "SELECT * FROM authors WHERE id = :id";
        // Execute SQL and return the Author array
        return fetchAuthorFromDB($authorId);
    }
]
?>

7. Traversing Arrays (One-to-Many)

Relationships are not always 1-to-1. Often they are 1-to-Many.

Schema:

graphql
1234
type Author {
  name: String!
  posts: [Post!]! # Array of posts
}

Query:

graphql
12345678
query {
  getAuthor(id: "5") {
    name
    posts {
      title
    }
  }
}

In this case, the posts resolver takes the Author's ID ($root['id']) and runs a SQL query like: SELECT * FROM posts WHERE author_id = 5.

8. The N+1 Problem (Performance Pitfall)

Consider this query fetching 100 posts:
graphql
123456
query {
  allPosts {
    title
    author { name }
  }
}

Execution:

  1. 1. The allPosts resolver runs: SELECT * FROM posts LIMIT 100 (1 query).
  1. 2. It returns 100 posts.
  1. 3. For *each* of the 100 posts, the author resolver runs.
  1. 4. It executes SELECT * FROM authors WHERE id = ? 100 individual times.

Total Database Queries: 101. This is the N+1 problem. It will crash your server under heavy load. The solution is using a tool called a DataLoader, which batches the 100 author IDs into a single query: SELECT * FROM authors WHERE id IN (1,2,3...100).

9. Best Practices

  • Never nest infinitely: Be careful allowing recursive relationships (User -> Friends -> Friends -> Friends). Implement Query Depth Limiting (covered in Chapter 16) to prevent malicious users from crashing your database.
  • Use DataLoaders: If you are building a production GraphQL API in PHP, look into the overblog/dataloader-php package to solve the N+1 problem immediately.

10. Common Mistakes

  • Storing objects instead of IDs in the DB: In a relational database, you store the author_id in the posts table. Beginners sometimes try to store the whole JSON author object. Let GraphQL do the joining via resolvers.
  • Putting SQL JOINs in the Root Resolver: While you *can* do a SQL JOIN in the getPost resolver to fetch the author, it breaks GraphQL's modular design. Keep resolvers focused on their specific type.

11. Mini Exercises

  1. 1. In the phrase "Parent-Child Relationship", if Order contains Customer, which is the parent and which is the child?
  1. 2. If you are writing a resolver for Customer inside an Order, which resolver argument contains the customer_id?

12. Coding Challenges

Challenge 1: You have a Team type and a Player type. A Team has a field called roster which returns [Player!]!. Write the conceptual SQL query that the roster resolver would execute to find the players, assuming you can access the Team's ID via $root['id'].

13. MCQs with Answers

Question 1

How does a child resolver know which parent it belongs to?

Question 2

What is the N+1 problem in GraphQL?

Question 3

How do you define a One-to-Many relationship in a GraphQL Schema?

14. Interview Questions

  • Q: Explain how a GraphQL server resolves a query that asks for a User and their list of Posts.
  • Q: What is the N+1 problem, and what is the standard tool/technique used to solve it in GraphQL?
  • Q: Should you use SQL JOINs in your top-level GraphQL resolvers, or let child resolvers fetch their own data? Why?

15. FAQs

Q: Does GraphQL replace SQL JOINs entirely? A: Conceptually, yes. GraphQL handles the "joining" of data at the application layer rather than the database layer. However, for massive datasets, you might still optimize with SQL JOINs under the hood.

Q: Can a child query the parent? A: Yes. A Post can have an Author field, and an Author can have a Posts field. As long as your resolvers are written correctly, GraphQL can traverse back and forth seamlessly.

16. Summary

In this chapter, we learned how to unlock the true power of GraphQL: traversing the Graph. We discovered that by using nested selection sets, clients can ask for deeply relational data. We looked at how the backend handles this using the $root resolver argument to link parents and children. Finally, we identified the dangerous N+1 performance problem that arises when fetching lists of relational data.

17. Next Chapter Recommendation

Writing out the same fields over and over for different queries can become tedious. To keep our frontend code clean and DRY (Don't Repeat Yourself), GraphQL has a built-in feature for reusability. Proceed to Chapter 12: GraphQL Fragments to learn how to modularize your queries.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·