15. Mutation input
5m

Overview

Let's finish up our !

In this lesson, we will:

  • Learn about common conventions for and the input type
  • Learn how to use the response to handle successful and failed actions

Mutation arguments & input

We've used a before in the Query.playlist — we passed in one called id.

GraphQL schema
type Query {
playlist(id: ID!): Playlist
}

For the addItemsToPlaylist , we'll need more than one .

From the documentation, we need the following parameters:

  • playlist_id - The ID of the playlist, as a string
  • position - An integer, zero-indexed, where we want to insert the track(s)
  • uris - A comma-separated string of uri values corresponding to the tracks we want to add

We could use all three as , but it's a good practice to use input types as for a .

The input type in a is a special that groups a set of together, and can then be used as an argument to another .

As a naming convention, we add the Input suffix to a type's name and give it the same name as the it's associated with.

In our case, we can name our to the addItemsToPlaylist as AddItemsToPlaylistInput. Let's go ahead and bring it to life!

The AddItemsToPlaylistInput type

Let's create a new file called add_items_playlist_input.py under the api/types folder and define a class called AddItemsToPlaylistInput.

api/types/add_items_to_playlist_input.py
import strawberry
class AddItemsToPlaylistInput:
...

We'll also need to define this class as a input type using the @strawberry.input decorator. Similar to the other Strawberry functions we've used so far, we can provide this function a name and description to use for the .

Let's go ahead and apply the @strawberry.input decorator to the class.

api/types/add_items_to_playlist_input.py
@strawberry.input
class AddItemsToPlaylistInput:
...

Next, we'll add properties. Remember, we need the ID of the playlist and a list of URIs, at the very minimum. We could also specify the position in the playlist these items get added, but it's not required for the REST API. By default, tracks will be appended to the end of the playlist, so we're safe to omit it from our . Remember, your GraphQL API does not need to match your REST API exactly!

api/types/add_items_to_playlist_input.py
playlist_id: strawberry.ID = strawberry.field(description="The ID of the playlist.")
uris: list[str] = strawberry.field(description="A list of Spotify URIs to add.")

Updating the resolver

Now let's make sure our knows about this input. Back in api/mutation.py, we can add it to the parameters of our function. We'll name this input.

Note that the input parameter can be named anything, like playlist_tracks or tracks_input for example! We recommend collaborating with your team to decide on naming conventions. Using input as the name is a common convention.

api/mutation.py
def add_items_to_playlist(
self,
input: AddItemsToPlaylistInput
) -> AddItemsToPlaylistPayload:
...

Since we're here, let's also make the function async and add the info parameter. We'll need this later on to access the spotify_client.

api/mutation.py
async def add_items_to_playlist(
self,
input: AddItemsToPlaylistInput,
info: strawberry.Info
) -> AddItemsToPlaylistPayload:
...

Time to use our service and our input type! In the body of the function, we'll use the add_tracks_to_playlist.asyncio method of our mock_spotify_rest_api_client package.

api/mutation.py
client = info.context["spotify_client"]
await add_tracks_to_playlist.asyncio(
playlist_id=input.playlist_id, uris=",".join(input.uris), client=client
)

Using the method signature as a guide, we can add the corresponding value from the input for playlist_id and uris. For the last parameter, we'll need to tweak the format a little. Since input.uris is a list of str types and the method expects a str type of comma-separated values, we'll use ",".join.

That's just the nature of the REST endpoint we're working with. To retrieve the playlist object, we do need to make a follow-up call using the get_playlist method we've used previously.

But where should we make that call? If we include it in this , that means an additional REST call even when the playlist isn't included in the !

We've already been through this situation before, where we made use of the chain. However, in this case, we're going to keep the REST call included in this resolver. Thinking about the client app's needs, if they are adding tracks to a playlist, they will most likely include the the playlist and its list of tracks in the ! They want to see the results of their after all.

Let's keep going. Inside the add_items_to_playlist , we'll make a call to the get_playlist function.

api/mutation.py
data = await get_playlist.asyncio(playlist_id=input.playlist_id, client=client)
playlist = Playlist(id=data.id, name=data.name, description=data.description)

Look familiar? It's the code we used in our Query.playlist .

Lastly, let's update the return value in our AddItemsToPlaylistPayload instance, specifically replacing the hard-coded playlist with the playlist instead.

api/mutation.py
return AddItemsToPlaylistPayload(
code=200,
success=True,
message="Successfully added items to playlist.",
playlist=playlist,
)

That's the success path taken care of! Now what about when something fails and an error pops up? Let's wrap our code so far in a try block and except any Exceptions that get thrown.

api/mutation.py
try:
await add_tracks_to_playlist.asyncio(
playlist_id=input.playlist_id, uris=",".join(input.uris), client=client
)
data = await get_playlist.asyncio(playlist_id=input.playlist_id, client=client)
playlist = Playlist(id=data.id, name=data.name, description=data.description)
return AddItemsToPlaylistPayload(
code=200,
success=True,
message="Successfully added items to playlist.",
playlist=playlist,
)
except Exception as e:
...

If something goes wrong, our return value should look a little different. We'll still return a AddItemsToPlaylistPayload type, but this time, the code will be 500, the success status will be false and we'll return whatever message the exception has. The playlist parameter will be set to None.

api/mutation.py
return AddItemsToPlaylistPayload(
code=500,
success=False,
message=str(e),
playlist=None,
)

Finally, let's not forget all the imports we need at the top of the file:

api/mutation.py
from .types.add_items_to_playlist_input import AddItemsToPlaylistInput
from mock_spotify_rest_api_client.api.playlists import (
add_tracks_to_playlist,
get_playlist,
)

Explorer time!

With the server running with our latest changes, let's start a new workspace tab and build our from scratch. With the addition of the input , when we add the addItemsToPlaylist , the looks a little different:

http://localhost:8000

Explorer - AddItemsToPlaylist mutation

GraphQL operation
mutation AddItemsToPlaylist($input: AddItemsToPlaylistInput!) {
addItemsToPlaylist(input: $input) {
}
}

We can also see the Variables section with an input property in the JSON object:

Variables
{
"input": null
}

Let's start to fill in this input object. In the Documentation panel, click into the input and add the three fields from AddItemsToPlaylistInput. Explorer will automatically update the for you.

http://localhost:8000

Explorer - AddItemsToPlaylist mutation adding variables for input

Variables
{
"input": {
"playlistId": null,
"uris": null
}
}

All we need to do is update those nulls! We'll use a new playlist ID and add an array with one URI in it. This is all mock data, so you can put whatever you want in there as a placeholder. It doesn't need to actually exist in the Spotify database.

Variables
{
"input": {
"playlistId": "6LB6g7S5nc1uVVfj00Kh6Z",
"uris": ["ASongAboutGraphQL"]
}
}
http://localhost:8000

Explorer - AddItemsToPlaylist mutation adding variables for input

Lastly, let's fill in the rest of the with the we need. Add the code, success and message . Then for a playlist, add its details and its tracks!

GraphQL operation
mutation AddItemsToPlaylist($input: AddItemsToPlaylistInput!) {
addItemsToPlaylist(input: $input) {
code
message
success
playlist {
id
name
tracks {
id
name
}
}
}
}

That's a big , press play to run! We should see our data come back successfully. At the bottom of the tracks list, we'll see the id set as the uri we passed in our , and the name as well.

http://localhost:8000

Explorer - AddItemsToPlaylist mutation adding variables for input

Note: Does your response look a little different? Because the REST API is shared across all learners, you may see more or less tracks in your response. This is likely because other learners have added their own tracks to the playlist. We also reset the data regularly to keep things clean.

Amazing! Now check out what happens if you update the playlist ID to a value that doesn't exist.

Variables
{
"input": {
"playlistId": "DoesNotExist",
"uris": ["ASongAboutGraphQL"]
}
}
http://localhost:8000

Explorer - AddItemsToPlaylist mutation adding variables for input

When we run the again, we'll still receive data, but this time we've got an error code, a failure message and a null playlist.

Our mutation is working! 🎉

Practice

How can we use the input type in our schema?
When creating an input type for a mutation, what naming convention is commonly used?

Key takeaways

  • in often require multiple to perform actions. To group arguments together, we use a GraphQL input type for clarity and maintainability.
  • Strawberry uses @strawberry.input to mark Python classes as input types.
  • We can access the input in the same way as any other argument in the function.

Conclusion

Bravo, you've done it, you've built a API! You've got a working jam-packed with playlists and tracks using the Spotify REST API as a . You've written queries and , and learned some common GraphQL conventions along the way. You've explored how to use GraphQL , , and input types in your schema design. Take a moment to celebrate; that's a lot of learning!

And if you've got any requests for what you'd like to see next, let us know below!

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.