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
Here's our next feature: the playlist page.
We'll most likely get to this page through a few ways: clicking on a specific playlist from the featured playlists page or maybe directly through a URL link.
However we get to this page, we'll need the playlist ID, which makes it a perfect use case for a GraphQL argument.
GraphQL 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 resolvers 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 new entry point
Pop open the schema.graphql
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.
Let's return to our Spotify REST API documentation to see how this works. 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 id
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.
Inside the Query
type in schema.graphql
update the playlist
field to include an id
argument.
"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 datasource!
Updating SpotifyAPI
Let's return to our SpotifyAPI
class in spotify-api.ts
and give it a new method that can reach out to this endpoint.
Just below our getFeaturedPlaylists
method, we'll add a new method called getPlaylist
that accepts a playlistId
parameter, which is a string
.
getPlaylist(playlistId: string) {// TODO}
This method will make a GET
request to the /playlists/{playlist_id}
endpoint we just tested. We can use the this.get
method, passing it the endpoint and including the playlistId
argument. Then, we'll return the results!
getPlaylist(playlistId: string) {return this.get(`playlists/${playlistId}`);}
Our method's return type still shows Promise<any>
. We know that the Promise
will resolve to a singular Playlist
type, so we can update that here.
getPlaylist(playlistId: string): Promise<Playlist> {return this.get(`playlists/${playlistId}`);}
Next up: the resolver function.
Using arguments in resolvers
Open up the resolvers.ts
file. Following the same structure as our schema, we'll add a new key inside the Query
object (just below the featuredPlaylists
function), named playlist
.
Query: {featuredPlaylists: (_, __, { dataSources }) => {return dataSources.spotifyAPI.getFeaturedPlaylists();},playlist: () => {}},
When we query our GraphQL API for the playlist
field, the id
argument we pass is automatically conveyed to this resolver.
To access the argument, we need to use resolver's second position parameter, args
. args
is an object that contains all GraphQL arguments that were provided for the field. We can destructure this object to access the id
property. We'll also need to destructure the third positional parameter for its dataSources
property.
playlist: (_, { id }, { dataSources }) => {},
Next, in the body of the resolver function, we'll use the dataSources.spotifyAPI.getPlaylist
, passing it the id
.
playlist: (_, { id }, { dataSources }) => {return dataSources.spotifyAPI.getPlaylist(id);},
We need to update our generated types to account for this new playlist
field in our schema, but while we're here, let's update our project so it automatically regenerates types every time our schema changes.
Jump into package.json
. We're going to tweak our dev
and generate
scripts.
"dev": "concurrently \"ts-node-dev --respawn --watch ./**/*.graphql ./src/index.ts\" \"npm run generate --watch\"",
This update lets us run two scripts concurrently: the first is ts-node-dev
, which we used previously. This command reboots our app from index.ts
anytime a change is made in the project. The second script that we run is npm run generate
, with a --watch
flag.
Next, let's update our generate
script.
"generate": "graphql-codegen --watch \"src/schema.graphql\""
This addition lets us tell the GraphQL Code Generator explicitly to watch our src/schema.graphql
file, and rerun the codegen process. As a result, anytime our project changes, we'll get the same hot reload we've enjoyed—along with an updated types.ts
file anytime our schema changes!
Cool, let's try it out. Restart your server with the following command:
npm run dev
We should see a lot more output—first and foremost, that types have been generated—followed by the usual output that the server is running.
Testing the playlist
field
Let's return to the Explorer at http://localhost:4000!
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!
Practice
Drag items from this box to the blanks above
resolvers
!
hardcoded
$
arguments
@
graph
name
schema
null
Key takeaways
- Query arguments allow us to filter, customize, and further specify the data we'd like to query.
- We can access a field's query arguments through its resolver function's second positional parameter,
args
. - 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.
Up next
We hear you, you're ready for some jams! We'll add the playlist's tracks in the next lesson.
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.