Overview
We can now query data from our REST API directly, but a list of featuredPlaylists
is all we can ask about. Let's give our querying capabilities another option!
In this lesson, we will:
- Explore query arguments in GraphQL
- Supplement the schema's
Query
type with an additional endpoint - Pass variables into a GraphQL query
Introducing query arguments
Our Query
type has just a single entry point: featuredPlaylists
.
type Query {featuredPlaylists: [Playlist!]!}
This means that while we can get a list of the featured playlists from our REST API, we don't yet have a way to ask for a specific playlist's details. This single Query
entry point doesn't meet the needs of all our mockups.
To resolve this, we'll need to add another entry point to our schema.
Pop open the schema.graphqls
file, and add the following field to the Query
type.
playlist: Playlist
We'll be able to specify which unique playlist we're querying for by giving this field an argument.
🤔 How to use arguments
An argument is a value you provide for a particular field in your query. The schema defines the arguments that each of your fields accepts.
Your datafetchers can then use a field's provided arguments to help determine how to populate the data for that field. Arguments can help you retrieve specific objects, filter through a set of objects, or even transform the field's returned value. A query that performs a search usually provides the user's search term as an argument.
To define an argument for a field in our schema, we add parentheses after the field name. Inside, we write the name of the argument followed by a colon, then the type of that argument, like String
or Int
. If we have more than one argument, we can separate them with commas.
🙌 Arguments in action
Let's return to our Spotify REST API documentation. In the list of endpoints, we'll find GET /playlists/{playlist_id}
. This operation accepts a playlist_id
parameter and returns the corresponding playlist object (if one exists).
Let's try it out!
Querying the /playlists/{playlist_id}
endpoint
Pass in the following playlist ID, and click Execute.
6LB6g7S5nc1uVVfj00Kh6Z
Here's a snippet of some of the properties included in the response:
{"id": "6LB6g7S5nc1uVVfj00Kh6Z","name": "Zesty Culinary Harmony","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.","tracks": {"items": [/* an array of track objects */]// more track properties},"collaborative": false// more playlist properties}
Everything we need is here—along with some additional properties we'll put to use soon!—so we can start building out our feature.
Adding the playlist
field
Inside the Query
type in schema.graphqls
update the playlist
field to:
"Retrieves a specific playlist."playlist(id: ID!): Playlist
And that's it for the query definition! We now have our schema up-to-date for the feature we're implementing. Onwards to the datafetcher!
Using arguments in datafetchers
The last datafetcher method we defined in the PlaylistDataFetcher
class was featuredPlaylists
, which maps to our Query
type's featuredPlaylists
field. Following that pattern, we'll add a new method for our playlist
field just below. Because this is still a Query
type field we're talking about, we'll keep the @DgsQuery
annotation.
public class PlaylistDataFetcher {// ...featuredPlaylist method@DgsQuerypublic MappedPlaylist playlist() {}}
Note: This method returns a MappedPlaylist
instance, rather than an instance of the generated Playlist
class, so we can benefit from the additional logic that maps through JSON responses and sets properties where we need them. MappedPlaylist
still matches the shape of our Playlist
GraphQL type, so this does not violate the rules of our schema.
When we query our GraphQL API for the playlist
field, the id
argument we pass is automatically conveyed to this datafetcher. (Remember, the method's name playlist
needs to match its corresponding Query
field exactly!)
To receive it, and actually do something with it, we'll specify that it receives an id
of type String
.
@DgsQuerypublic MappedPlaylist playlist(String id) {}
In order to clarify that this parameter corresponds with the id
input we specified in our schema's playlist
field, we need to add a specific DGS annotation called @InputArgument
.
Let's import it...
import com.netflix.graphql.dgs.InputArgument;
...and update our method.
public MappedPlaylist playlist(@InputArgument String id) {}
We'll use our class' instance of the SpotifyClient
class to make a call to this new endpoint, and pass it the id
argument. First, we need to make sure the SpotifyClient
class knows about this endpoint, how to pass along the necessary data, and what to do with the response.
Updating the SpotifyClient
Back in datasources/SpotifyClient
, let's make a new method called playlistRequest
that can manage the call to the endpoint for a specific playlist. It will receive a String
type variable called playlistId
, and return an instance of MappedPlaylist
.
public MappedPlaylist playlistRequest(String playlistId) {}
Our file hasn't used MappedPlaylist
yet, so let's also import it at the top.
import com.example.soundtracks.models.MappedPlaylist;
This endpoint is still a GET
operation, we'll chain on a few methods: get()
, uri()
, and retrieve()
. Because our endpoint requires an actual playlist ID, we'll include the playlistId
we pass into this method as the second parameter to uri
.
public MappedPlaylist playlistRequest(String playlistId) {return client.get().uri("/playlists/{playlist_id}", playlistId).retrieve()}
Finally, we'll map the response to the MappedPlaylist
class.
public MappedPlaylist playlistRequest(String playlistId) {return client.get().uri("/playlists/{playlist_id}", playlistId).retrieve().body(MappedPlaylist.class);}
Note: Just as we did for the featuredPlaylistsRequest
, we've made the name of playlistRequest
extra verbose to avoid confusion with the playlist
method on PlaylistDataFetcher
. These are separate methods, and we'll call this playlistRequest
method from our playlist
datafetcher shortly.
Updating the datafetcher
Time to jump back to our datafetcher, and call this new method on SpotifyService
! Inside of the playlist
method, we'll pass in the id
argument and return the results.
@DgsQuerypublic MappedPlaylist playlist(@InputArgument String id) {return spotifyClient.playlistRequest(id);}
Let's test it out! Restart your server, then return to the Explorer to write out a new query.
Testing the playlist
field
In the Documentation panel we'll see that our Query
type contains our new playlist
field. When we click into it we can even see the name and the type of data it receives as an argument. Let's add a new workspace tab, then click the plus button beside the playlist
field to add it to our query.
The Explorer automatically inserts some syntax for us to make completing the query easier.
query Playlist($playlistId: ID!) {playlist(id: $playlistId) {}}
You'll notice something new here: a dollar sign ($
) followed by the name playlistId
.
The $
symbol indicates a variable in GraphQL. The name after the $
symbol is the name of our variable, which we can use throughout the query. After the colon is the variable's type, which must match the type of the argument we'll use it for. Variables are great—they let us pass argument values dynamically from the client-side so we don't have to hardcode values into our query. We'll use them every time we create a query with arguments.
In our case, we have a variable called playlistId
that the Explorer set up for us down in the Variables section. Right now, it's set to null
, but let's replace it with the playlist ID we've been testing so far: 6LB6g7S5nc1uVVfj00Kh6Z
.
Add the following to the Variables section in the Explorer:
{ "playlistId": "6LB6g7S5nc1uVVfj00Kh6Z" }
Let's test out our query by adding a few more fields for the playlist we're after: name
and description
.
The Operation panel of the Explorer should now look like this:
query GetPlaylist($playlistId: ID!) {playlist(id: $playlistId) {namedescription}}
When we click on the run query button, we see the data we're expecting!
This works great, but our schema's MappedPlaylist
type still feels a little lacking. After all, playlists are meant to contain something, right? Circling back to the mockup—we can see that we're missing data about tracks!
Practice
Drag items from this box to the blanks above
$
!
hardcoded
@
datafetchers
graph
name
null
schema
arguments
Key takeaways
- Query arguments allow us to filter, customize, and further specify the data we'd like to query.
- We can refer to arguments passed directly into GraphQL fields with the DGS
@InputArgument
annotation. - We can use the
$
symbol in the Explorer to specify query variables.
Up next
There's plenty more data to pull from in our REST API's endpoints, but we need our GraphQL schema to keep pace. In the next lesson, we'll explore how we build a relationship between object types—specifically, between our Playlist
GraphQL type and a new type we'll call Track
.
Share your questions and comments about this lesson
This course is currently in
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.