11. A playlist's tracks
5m

Overview

We're still missing something: a playlist's tracks — the songs we actually want to listen to!

In this lesson, we will:

  • Create the Track type
  • Write the function for a playlist's tracks

The Track object

Mockup design of a playlist's tracks

Take a few moments to study the mockup above and start thinking of what pieces of data we'll need, and what types they might be. We'll ignore the artist information for now to keep things simpler.

You might also want to check out the GET /playlists/{playlist_id}/tracks endpoint for a better idea of what the API returns and what they've named certain properties.

Take your time!

When you're ready, compare it with what we came up with:

Mockup design of a playlist's tracks

  • The track's name is a string.
  • The "E" label denotes if the track is explicit. We can make that a bool type and let the client use the logic to display an "E" label or not.
  • The duration of the track. The mockup shows the formatting to be in minutes and seconds with a colon in between, so maybe we might need to make this a string type, or maybe the client team wants to control their own formatting and we should return it as a double type with the duration in milliseconds. The REST endpoint returns the latter, so let's stick with that for now.
  • Though it's not shown on the mockup, it's helpful to have an identifier for an object, so we'll make sure to return the track's ID as well.
  • There's an option to copy a link to the track to share with others so they can open it on Spotify as well. So we'll probably need to return that link as a string. In the REST API, they've named this uri.
  • We'll make all of these non-nullable since the REST API does the same.

Your design might have looked a little different from ours, but with these pieces in mind, let's go ahead and create our Track class! Remember, we're keeping all our types organized together under the Types folder.

You should have everything you need to know to try writing this one out yourself! If you need a reference, feel free to use the one below:

Types/Track.cs
namespace Odyssey.MusicMatcher;
[GraphQLDescription("A single audio file, usually a song.")]
public class Track
{
[ID]
[GraphQLDescription("The ID for the track.")]
public string Id { get; }
[GraphQLDescription("The name of the track.")]
public string Name { get; set; }
[GraphQLDescription("The track length in milliseconds.")]
public double DurationMs { get; set; }
[GraphQLDescription(
"Whether or not the track has explicit lyrics (true = yes it does; false = no it does not OR unknown)"
)]
public bool Explicit { get; set; }
[GraphQLDescription("The URI for the track, usually a Spotify link.")]
public string Uri { get; set; }
public Track(string id, string name, string uri)
{
Id = id;
Name = name;
Uri = uri;
}
}

Tip: When choosing names in your schema, try to be descriptive but concise. For example, we could have chosen duration as one of the names. However, durationMs is a bit more descriptive on the format of the returning in milliseconds. We could have also chosen durationInMilliseconds for further clarity. If we wanted to also support returning a formatted string, we could add a new called durationString. Using descriptions also helps with clarity. Learn more about schema naming conventions in the Apollo documentation.

Don't forget to add descriptions to the schema using the [GraphQLDescription] attribute.

Connecting tracks to a playlist

With our Track class all set up, we can now add the long awaited tracks to Playlist.

Playlist.cs
[GraphQLDescription("The playlist's tracks.")]
public List<Track> Tracks { get; set; }

Let's think about this particular function. So far, the Playlist class contains simple property (for Id, Name and Description). Remember, behind the scenes, Hot Chocolate converts properties with get accessors to .

Can we do the same for our Tracks ? Well, let's examine where our data is coming from. For this next section, we highly recommend using your code editor's features to Cmd/Ctrl + click on a particular type to navigate to its type navigation!

The details for a particular playlist are coming from the Query.Playlist function, which uses the SpotifyService method GetPlaylistAsync.

If we follow the path in our SpotifyService.cs file, we can see that the response returns a SpotifyWeb.Playlist type. This type has a property Tracks of type PaginatedOfPlaylistTrack. PaginatedOfPlaylistTrack then has a number of other properties related to pagination, and a property called Items, which is a collection of PlaylistTrack types.

Following that type, we get more properties related to the playlist track's metadata, like who added it and when, as well as a property called Track, which is of type PlaylistTrackItem. Finally, this looks like the Track information we're looking for! Among other properties, it has Id, Name, Explicit, Duration_ms properties, which match exactly what our Track class in our has.

Whew! All that to say, we have to dig a couple levels deeper to return the values we need.

However, we've actually done the first level already. We've turned a SpotifyWeb.Playlist type into our own Playlist class using a constructor.

Playlist.cs
public Playlist(SpotifyWeb.Playlist obj)
{
Id = obj.Id;
Name = obj.Name;
Description = obj.Description;
}

So we can expand on this constructor and initialize the Playlist.Tracks property using the SpotifyWeb.Playlist obj .

Let's take this one step at a time! Make sure to lean on your code editor's IntelliSense to get a better sense of what properties are available at each level.

Inside the Playlist(SpotifyWeb.Playlist obj) constructor, we'll first extract the collection of paginated tracks.

Playlist.cs
var paginatedTracks = obj.Tracks.Items;

This paginatedTracks is currently a collection of SpotifyWeb.PlaylistTrackItem objects. This is very close to what we need! We need our own Track class. This should be a familiar pattern at this point; we've used the same pattern twice before.

We'll create a new, additional Track constructor that takes a SpotifyWeb.PlaylistTrackItem object and initializes its based on that object's properties. Jumping to the Track.cs file:

Track.cs
public Track(PlaylistTrackItem obj)
{
Id = obj.Id;
Name = obj.Name;
DurationMs = obj.Duration_ms;
Explicit = obj.Explicit;
Uri = obj.Uri;
}

Don't forget to import the SpotifyWeb namespace at the top, since PlaylistTrackItem is coming from that package.

Track.cs
using SpotifyWeb;

We can now use this Track constructor back in our Playlist constructor. We'll map over those items and return a new collection, creating a Track instance from item.Track.

Playlist.cs
var trackObjects = paginatedTracks.Select(item => new Track(item.Track));

Finally, we'll convert to a List type because that's what the Tracks property is expecting.

Playlist.cs
Tracks = trackObjects.ToList();

Putting it all together, here's what the new expanded Playlist constructor looks like:

Playlist.cs
public Playlist(SpotifyWeb.Playlist obj)
{
Id = obj.Id;
Name = obj.Name;
Description = obj.Description;
var paginatedTracks = obj.Tracks.Items;
var trackObjects = paginatedTracks.Select(item => new Track(item.Track));
Tracks = trackObjects.ToList();
}

Want something a little shorter? We can simplify into a one-liner:

Playlist.cs
public Playlist(SpotifyWeb.Playlist obj)
{
Id = obj.Id;
Name = obj.Name;
Description = obj.Description;
Tracks = obj.Tracks.Items.Select(item => new Track(item.Track)).ToList();
}

Explorer time!

That was a lot of code, but we should have enough to for a playlist's tracks now! Make sure our server is running with the latest changes.

Back in Explorer, let's add to our original with for the playlist's tracks.

GraphQL operation
query GetPlaylistDetails($playlistId: ID!) {
playlist(id: $playlistId) {
id
name
description
tracks {
id
name
durationMs
explicit
uri
}
}
}

With the Variables section set to:

Variables
{
"playlistId": "6Fl8d6KF0O4V5kFdbzalfW"
}
https://studio.apollographql.com/sandbox/explorer

Explorer - get playlist's tracks

Wow, we've got so many tracks for this playlist!

Task!

An alternate path

Now what about the featuredPlaylists path? It's another entry point to our schema that returns a list of Playlist types, which then has access to its tracks . Let's try it out.

GraphQL operation
query GetFeaturedPlaylists {
featuredPlaylists {
id
name
description
tracks {
id
name
explicit
uri
}
}
}

When we run this , we get an errors array back with the message "Cannot return null for non-nullable field.". Uh-oh!

Key takeaways

  • functions may involve navigating through multiple levels of data, especially in scenarios with nested objects.
  • We recommend choosing descriptive yet concise names for the schema.

Up next

Let's investigate the source of that error and fix it.

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.