← Back to all tutorials

Related Data

Master working with related data in GraphQL — connecting types and resolving nested relationships.

Related Data in GraphQL

One of GraphQL's greatest strengths is how it handles relationships between data. In this episode, we'll explore how to model and query connected data types.

Our Data Model

Let's work with three interconnected types:

type Game {
  id: ID!
  title: String!
  platform: [String!]!
  reviews: [Review!]      # A game has many reviews
}

type Review {
  id: ID!
  rating: Int!
  content: String!
  game: Game!              # A review belongs to one game
  author: Author!          # A review belongs to one author
}

type Author {
  id: ID!
  name: String!
  verified: Boolean!
  reviews: [Review!]       # An author has many reviews
}

Relationship Types

RelationshipExampleSchema
One-to-ManyGame → Reviewsreviews: [Review!]
Many-to-OneReview → Gamegame: Game!
Many-to-ManyAuthor ↔ Games (via Reviews)Through join type

Setting Up the Data

let games = [
  { id: "1", title: "Zelda: TotK", platform: ["Switch"] },
  { id: "2", title: "Final Fantasy XVI", platform: ["PS5", "PC"] },
  { id: "3", title: "Elden Ring", platform: ["PS5", "Xbox", "PC"] },
];

let authors = [
  { id: "1", name: "Mario", verified: true },
  { id: "2", name: "Yoshi", verified: false },
  { id: "3", name: "Peach", verified: true },
];

let reviews = [
  { id: "1", rating: 9, content: "Amazing!", game_id: "2", author_id: "1" },
  { id: "2", rating: 10, content: "Masterpiece!", game_id: "3", author_id: "2" },
  { id: "3", rating: 7, content: "Good game", game_id: "1", author_id: "3" },
  { id: "4", rating: 5, content: "Too hard", game_id: "3", author_id: "1" },
  { id: "5", rating: 8, content: "Beautiful world", game_id: "1", author_id: "2" },
];

Writing Relationship Resolvers

The key is the parent argument. When resolving a nested field, the parent contains the data from the parent resolver:

const resolvers = {
  Query: {
    games: () => games,
    game: (_, args) => games.find(g => g.id === args.id),
    authors: () => authors,
    author: (_, args) => authors.find(a => a.id === args.id),
    reviews: () => reviews,
    review: (_, args) => reviews.find(r => r.id === args.id),
  },

  Game: {
    reviews(parent) {
      // parent.id is the current game's ID
      return reviews.filter(r => r.game_id === parent.id);
    },
  },

  Review: {
    game(parent) {
      // parent.game_id is the foreign key
      return games.find(g => g.id === parent.game_id);
    },
    author(parent) {
      return authors.find(a => a.id === parent.author_id);
    },
  },

  Author: {
    reviews(parent) {
      return reviews.filter(r => r.author_id === parent.id);
    },
  },
};

Deeply Nested Queries

Now you can traverse relationships as deep as you want:

{
  author(id: "1") {
    name
    verified
    reviews {
      rating
      content
      game {
        title
        platform
        reviews {
          rating
          author {
            name
          }
        }
      }
    }
  }
}

This single query says: "Get author #1, their reviews, the game for each review, ALL reviews for those games, and the author of each of those reviews." All in one request!

The Resolver Chain

Understanding the resolver chain is critical:

Query.author({}, { id: "1" })
  → Author.reviews(authorObj)
    → Review.game(reviewObj)
      → Game.reviews(gameObj)
        → Review.author(reviewObj)

Each resolver receives the result of its parent as the first argument.

Key Takeaways

  • GraphQL handles relationships through nested resolvers
  • The parent argument passes data down the resolver chain
  • You can query as deeply nested as needed
  • Foreign keys (like game_id) connect the data in resolvers
  • One query can traverse multiple relationship levels

Next up: Mutations — adding and deleting data!