Object Types and Relationships#
Learn how to create complex GraphQL object types using dataclasses, Pydantic models, and define relationships between them.
Basic Object Types#
For complex nested data structures, you can define custom object types using dataclasses or Pydantic models:
from dataclasses import dataclass
from pydantic import BaseModel
# Using dataclasses
@dataclass
class Address:
street: str
city: str
country: str
# Using Pydantic models
class User(BaseModel):
id: int
name: str
email: str
@api.type(is_root_type=True)
class Root:
@api.field
def user_address(self) -> Address:
return Address(street="123 Main St", city="New York", country="USA")
@api.field
def current_user(self) -> User:
return User(id=1, name="Alice", email="alice@example.com")Both dataclasses and Pydantic models are automatically converted to GraphQL object types with all their fields exposed.
Nested Object Relationships#
You can create complex nested structures by referencing other types:
@dataclass
class Author:
id: int
name: str
email: str
@dataclass
class Book:
id: int
title: str
isbn: str
author: Author # Nested relationship
@api.type(is_root_type=True)
class Query:
@api.field
def featured_book(self) -> Book:
author = Author(id=1, name="Alice Smith", email="alice@example.com")
return Book(
id=100,
title="GraphQL Guide",
isbn="978-0123456789",
author=author
)This creates a GraphQL schema with nested types:
type Author {
id: Int!
name: String!
email: String!
}
type Book {
id: Int!
title: String!
isbn: String!
author: Author!
}
type Query {
featuredBook: Book!
}Advanced Dataclass Relationships#
For more complex relationships, you can add methods to dataclasses using the standalone @field decorator:
from dataclasses import dataclass
from typing import List, Optional
from graphql_api.decorators import field
# Sample data (in real apps, this would be from a database)
authors_db = [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"},
]
posts_db = [
{"id": 1, "title": "First Post", "content": "Content", "author_id": 1},
{"id": 2, "title": "Second Post", "content": "More content", "author_id": 2},
]
@dataclass
class Post:
id: int
title: str
content: str
author_id: int
@dataclass
class Author:
id: int
name: str
email: str
@field
def get_posts(self) -> List[Post]:
"""Get all posts by this author."""
return [Post(**p) for p in posts_db if p["author_id"] == self.id]
# Add relationship method to Post after Author is defined
@field
def get_author(self) -> Optional[Author]:
"""Get the author of this post."""
author_data = next((a for a in authors_db if a["id"] == self.author_id), None)
if author_data:
return Author(**author_data)
return None
# Attach the method to the dataclass
Post.get_author = get_author
@api.type(is_root_type=True)
class Root:
@api.field
def posts(self) -> List[Post]:
return [Post(**p) for p in posts_db]
@api.field
def authors(self) -> List[Author]:
return [Author(**a) for a in authors_db]This creates a GraphQL schema with bi-directional relationships:
type Post {
id: Int!
title: String!
content: String!
authorId: Int!
getAuthor: Author
}
type Author {
id: Int!
name: String!
email: String!
getPosts: [Post!]!
}You can now query these relationships:
query {
posts {
id
title
getAuthor {
name
email
}
}
}
query {
authors {
name
getPosts {
title
}
}
}Pydantic Model Relationships#
Pydantic models can also define complex relationships:
from pydantic import BaseModel
from typing import List, Optional
class Author(BaseModel):
id: int
name: str
bio: Optional[str] = None
class Book(BaseModel):
id: int
title: str
author: Author
tags: List[str] = []
class Library(BaseModel):
name: str
books: List[Book]
featured_author: Optional[Author] = None
@api.type(is_root_type=True)
class Query:
@api.field
def library(self) -> Library:
author = Author(id=1, name="Jane Doe", bio="Science fiction writer")
books = [
Book(
id=1,
title="Space Adventures",
author=author,
tags=["sci-fi", "adventure"]
),
Book(
id=2,
title="Future Worlds",
author=author,
tags=["sci-fi", "dystopian"]
)
]
return Library(
name="Central Library",
books=books,
featured_author=author
)Circular References#
When dealing with circular references, use forward references with strings:
from __future__ import annotations # Enable forward references
@dataclass
class Department:
name: str
employees: List[Employee] # Forward reference
@dataclass
class Employee:
name: str
department: Department
# This works because Python resolves the types at runtimeFor more complex cases, you might need to handle circular references manually:
@dataclass
class User:
id: int
name: str
_friends: List[int] = field(default_factory=list) # Store IDs
@field
def friends(self) -> List['User']:
"""Get user's friends as User objects."""
return [get_user_by_id(friend_id) for friend_id in self._friends]Lazy Loading and N+1 Prevention#
For performance, implement lazy loading in your relationship methods:
@dataclass
class Author:
id: int
name: str
email: str
@field
def books(self) -> List[Book]:
"""Get books by this author with efficient loading."""
# Use batching or caching to prevent N+1 queries
return book_service.get_books_by_author(self.id)
@field
async def books_async(self) -> List[Book]:
"""Async version for better performance."""
return await book_service.get_books_by_author_async(self.id)Key Points About Object Relationships#
Using the standalone @field decorator:
- Import from
graphql_api.decorators - Methods without
@fieldare not exposed as GraphQL fields - You can add methods to dataclasses after definition for complex relationships
- Both sync and async methods are supported
- Docstrings become field descriptions in the schema
Performance considerations:
- Be mindful of N+1 query problems
- Use async resolvers for I/O-bound operations
- Consider implementing DataLoader patterns for batching
- Cache expensive computations
Type safety:
- Always use proper type hints
- Use
Optional[]for nullable relationships - Use
List[]for one-to-many relationships - Forward references work for circular dependencies
This gives you the foundation for building complex, interconnected GraphQL schemas with rich object relationships.
Building on This#
Object types work together with other GraphQL features:
Documentation: Documentation shows how to add rich documentation to your object types.
Data modifications: Input Types & Mutations covers handling data modifications for these object types.
Advanced patterns: Enums & Interfaces introduces polymorphism and advanced type modeling.