11. Adding the Track type
10m

Overview

We're getting our playlist data back from the REST API, but we haven't met the needs of our mockup. Our playlist objects—as far as we can see—don't actually contain any music! Let's fix that.

In this lesson, we will:

  • Introduce the Track type to our schema
  • for playlist and track details in a single

Building the Track type

As we learned in the lesson on syntax, on types don't have to return a basic type—they can also return other !

For instance, we can add a tracks to our Playlist type—but what's the appropriate return type?

"A curated collection of tracks designed for a specific activity or mood."
type Playlist {
"The ID for the playlist."
id: ID!
"The name of the playlist."
name: String!
"Describes the playlist, what to expect and entices the user to listen."
description: String
tracks: # What type should this be?
}

Putting our business glasses on, we can see how details for a track object—such as name, duration, and whether or not it's explicit—would come in handy. Multiple tracks could appear in multiple playlists, and we might want different views that show us all of the tracks in a single playlist. For these reasons, we need to think of a "track" as a standalone —in other words, we should make it its own type called Track.

Open up the schema.graphql file.

We'll update the Playlist type to include a tracks that returns a non-nullable list of Track types. We'll also add a description for the while we're here.

schema.graphql
"A curated collection of tracks designed for a specific activity or mood."
type Playlist {
"The ID for the playlist."
id: ID!
"The name of the playlist."
name: String!
"Describes the playlist, what to expect and entices the user to listen."
description: String
"The tracks of the playlist."
tracks: [Track!]!
}

Now, let's actually define what a Track looks like.

Mockup design of a playlist's tracks

We'll concern ourselves with just a few properties: id, name, durationMs, explicit, and uri. In the schema.graphql file, add the new Track type shown below:

schema.graphql
"A single audio file, usually a song."
type Track {
"The ID for the track."
id: ID!
"The name of the track"
name: String!
"The track length in milliseconds."
durationMs: Int!
"Whether or not the track has explicit lyrics (true = yes it does; false = no it does not OR unknown)"
explicit: Boolean!
"The URI for the track, usually a Spotify link."
uri: String!
}

And from the schema's perspective, our work is done!

After saving our changes, the codegen process should have run automatically. We can check types.ts to make sure that our new Track type has been added!

types.ts
/** A single audio file, usually a song. */
export type Track = {
__typename?: "Track";
/** The track length in milliseconds. */
durationMs: Scalars["Int"]["output"];
/** Whether or not the track has explicit lyrics (true = yes it does; false = no it does not OR unknown) */
explicit: Scalars["Boolean"]["output"];
/** The ID for the track. */
id: Scalars["ID"]["output"];
/** The name of the track */
name: Scalars["String"]["output"];
/** The URI for the track, usually a Spotify link. */
uri: Scalars["String"]["output"];
};
Task!

Testing the Track type

Jump back into the Explorer. We'll try running a that calls for a playlist's tracks details.

query Playlist($playlistId: ID!) {
playlist(id: $playlistId) {
name
description
tracks {
id
name
}
}
}

And make sure that in the Variables panel, our $playlistId is still set.

{ "playlistId": "6LB6g7S5nc1uVVfj00Kh6Z" }

But when we run the ... kaboom! A big error appears in the Response panel rather than the data we want. But what's the problem?

"Expected Iterable, but did not find one for field \"Playlist.tracks\"."

Revisiting the JSON response

To solve this mystery, we need to return to our REST API and take a closer look at the shape of our playlist object. Let's inspect that /playlists/{playlist_id} endpoint, passing in the following ID to get our response.

6LB6g7S5nc1uVVfj00Kh6Z

What properties do you see on the playlist object?

Response from /playlists/{playlist_id}
{
"collaborative": false,
"description": "Infuse flavor into your kitchen. This playlist merges zesty tunes with culinary vibes, creating a harmonious background for your cooking escapades. Feel the synergy between music and the zest of your creations.",
"id": "6LB6g7S5nc1uVVfj00Kh6Z",
"name": "Zesty Culinary Harmony",
// ... other properties
"tracks": { ... }
}

At first glance, it looks like everything we need is there—id, name, description, and even tracks. But when we drill into the tracks property, we'll see something we don't expect—it's not an array of track objects at all, but another object!

Inspecting the 'tracks' key
{
"tracks": {
"href": "https://...",
"limit": 100,
"next": null,
"items": [
{
"track": {
"id": "2epbL7s3RFV81K5UhTgZje",
"name": "Lemon Tree",
"uri": "spotify:track:2epbL7s3RFV81K5UhTgZje"
// other track properties
}
}
/* additional track objects */
]
}
}

We don't find our actual track objects (or at least the data we want!) until we drill even further into this object's items property.

Furthermore, each object contained in items has a track property we need to delve into. How do we get around this mismatch between our REST API responses, and the shape of the data we specified in our schema?

Retrieving tracks data

It's clear that we need to do some digging through our JSON response to grab the tracks for a particular playlist. The only question is: where should that extra logic live?

One option would be to add quite a bit more code to our Query.playlist function. Instead of returning the response from the REST API directly, we'd need to iterate through all of the individual track details, finally returning them on a new object that contained all of the playlist properties we need: id, name, description, and tracks!

One possible implementation to return tracks
// Original implementation
// playlist: (_, { id }, { dataSources }) => {
// return dataSources.spotifyAPI.getPlaylist(id);
// },
playlist: async (_, { id }, { dataSources }) => {
const {
id: playlistId,
name,
description,
tracks: { items = [] } = {},
} = await dataSources.spotifyAPI.getPlaylist(id);
const newTrackItems = items.map(({ track }: { track: Track }) => {
const { id, name, duration_ms, explicit, uri } = track;
return { id, name, durationMs: duration_ms, explicit, uri };
});
return { id: playlistId, name, description, tracks: newTrackItems };
},

This technically works. But with this approach, our playlist is burdened with a lot of extra logic it might not always require. Take, for instance, a that doesn't ask for a playlist's tracks.

Querying playlist without tracks
query Playlist($playlistId: ID!) {
playlist(id: $playlistId) {
name
description
}
}

Even though this doesn't include the playlist.tracks , our function would still go to all the trouble of locating that data, plucking it from nested JSON objects, and returning it.

Furthermore, what happens when we try to featuredPlaylists and all of their tracks? We'd have to duplicate all of the track-specific logic to the featuredPlaylists as well!

query FeaturedPlaylists {
featuredPlaylists {
name
tracks {
name
}
}
}

Now we're duplicating code, and worse, we're executing code even when it's not required.

Fortunately, there's a better approach. It lets us keep our thin and concerned exclusively with the data they need to provide. Let's talk about resolver chains.

Key takeaways

  • An 's can return types or other object types.
  • When a on an returns another , we can write complex queries that traverse from one object to another—no follow-up queries necessary!

Up next

Let's see how we can keep our lean and focused in the next lesson.

Previous

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.