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
| Relationship | Example | Schema |
|---|---|---|
| One-to-Many | Game → Reviews | reviews: [Review!] |
| Many-to-One | Review → Game | game: Game! |
| Many-to-Many | Author ↔ 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
parentargument 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!