Setup
In this section we'll see some techniques that we can use to optimize our data fetching. But before we start we need to add more fields to our API.
Currently we are only able to fetch a list of podcasts and a single podcast. We'll add a field to fetch the latest episodes (globally) and we'll see how we can prevent issues like N+1 queries.
The Episode type
Let's start by adding a new type to our API. We'll be adding a new type called
Episode
that will represent a single episode.
We'll be adding it to api/podcasts/types.py
and it will look like this:
from datetime import datetime
import strawberry
from db import models
@strawberry.type
class Podcast:
id: strawberry.ID
title: str
description: str
@classmethod
def from_db(cls, db_podcast: models.Podcast) -> "Podcast":
return cls(
id=strawberry.ID(db_podcast.id),
title=db_podcast.title,
description=db_podcast.description,
)
@strawberry.type
class Episode:
id: strawberry.ID
title: str
notes: str
published_at: datetime
podcast_id: strawberry.Private[str]
@strawberry.field
async def podcast(self) -> Podcast:
from db import data
db_podcast = await data.find_podcast_by_id(self.podcast_id)
assert db_podcast is not None
return Podcast.from_db(db_podcast)
@classmethod
def from_db(cls, db_episode: models.Episode) -> "Episode":
return cls(
id=strawberry.ID(db_episode.id),
title=db_episode.title,
notes=db_episode.notes,
published_at=db_episode.published_at,
podcast_id=db_episode.podcast_id, # type: ignore
)
There's a couple of things to notice here:
- we are using the
strawberry.Private
type to mark thepodcast_id
field as private. This means that it won't be exposed in the GraphQL schema. We are using it because we don't want to expose thepodcast_id
field in the GraphQL schema, we want to expose thepodcast
field instead. - we are using the
strawberry.field
decorator to define thepodcast
field. This is because we need to fetch the podcast from the database, and if we did that in thefrom_db
method we would be doing a database query for each episode, even if the client hasn't requested the podcast field.
Adding the new query
Now that we have the Episode
type we can add a new query to fetch the latest
episodes.
We'll be adding it to api/podcasts/query.py
and it will look like this:
@strawberry.type
class PodcastsQuery:
# ... keep the other fields
@strawberry.field
async def latest_episodes(self, last: int = 5) -> List[Episode]:
episodes = await data.find_latest_episodes(last=last)
return [Episode.from_db(episode) for episode in episodes]
Note: we aren't using the relay pagination here to keep things simple for this workshop.
Testing the query
Now that we have the new query we can test it in GraphiQL.
We can run the following query:
{
latestEpisodes {
title
}
}
and we should get the latest 5 episodes. Let's change the query to also fetch the podcast and its title:
{
latestEpisodes {
title
podcast {
title
}
}
}
Nice! We can see that we are able to fetch the data about the podcast in a single GraphQL query, and when we are not fetching the podcast we are not doing any (additional) database queries.
Unfortunately when we are fetching the podcast we are doing a database query for
each episode. This is because we are fetching the podcast in the podcast
field, and we are doing that for each episode.
In the next section we'll see how we could use the information about the GraphQL operation to optimize the query.