diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..de66e4fad3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "spellright.language": [], + "spellright.documentTypes": ["latex", "plaintext"], + "cSpell.words": ["Graphi", "graphiql"], + "editor.tabSize": 2 +} diff --git a/docs/guides/interacting-with-spotify.md b/docs/guides/interacting-with-spotify.md new file mode 100644 index 0000000000..8917674978 --- /dev/null +++ b/docs/guides/interacting-with-spotify.md @@ -0,0 +1,400 @@ +--- +title: Interacting with Spotify +--- + +This guide is aimed to demonstrate how you can interact seamlessly with the Spotify REST API using Tailcall's `@http` operator. Not only that, but we will also be using other operators such as `@cache` to demonstrate how you can harness the power of Tailcall's operators to build a powerful and efficient application. + +## Roadmap + +We would be building a simple server that would allow us to do the following using Spotify's REST API: + +- Get an album's details +- Get tracks of an album +- Save an album +- Get a user's saved albums +- Remove an album from a user's saved albums + +## Getting started + +### Set up development environment + +First, let's create a new file that we will be using to write our schema. For this guide, we will be using a `.graphql` schema. So, let's create a file named `spotify.graphql`. + +You would need to install the [Tailcall CLI](../getting_started/installation.mdx) to proceed any further. Once you have that set up, proceed to the next section. + +### Obtaining Spotify Access Token + +[This](https://developer.spotify.com/documentation/web-api#getting-started) section of Spotify API's documentation explains how you can obtain an access token. Once you have the access token, we will be using it as a `Bearer` token, which we will be passing in the `Authorization` header of our requests. This token enables spotify to identify the user on whose behalf the request is being made. + +## Interacting with the API + +### Setting up the types + +Once we have got our file set up, we will have to set up the types that we will be using in our schema. For this guide, we will be using the following types: + +```graphql showLineNumbers +type AlbumInput { + ids: [String]! +} + +type DeleteTrackInput { + tracks: [TrackUri] + snapshot_id: String +} + +type AddTrackInput { + uris: [String]! +} + +type TrackUri { + uri: String! +} + +type Artist { + id: String! + name: String! + type: String! + uri: String! +} + +type Track { + id: String! + name: String! + type: String! + uri: String! + href: String + artists: [Artist]! +} + +type Album { + id: String! + name: String! + type: String! + uri: String! + href: String + images: [Image]! + total_tracks: Int! + tracks: TrackPage + artists: [Artist]! +} + +type Image { + height: Int! + url: String! + width: Int! +} + +interface Page { + total: Int! + limit: Int! + offset: Int! + previous: String + next: String + href: String +} + +type TrackPage implements Page { + items: [Track]! + total: Int! + limit: Int! + offset: Int! + previous: String + next: String + href: String +} + +type AlbumPage implements Page { + items: [Album]! + total: Int! + limit: Int! + offset: Int! + previous: String + next: String + href: String +} +``` + +You can copy and paste the content into your `spotify.graphql` file. + +While some of the types such as `Album`, `Track` may be self explanatory, the other types would become clearer as we use them in our schema. **Note that, these types are a subset of what the Spotify API returns. We have only included the fields that we would be using in our schema.** + +### Setting up our server + +Tailcall provides us with a [`@server` operator](../operators/server.md) that allows us to set up a GraphQL server. There are a lot of parameters that we can pass to the `@server` operator to configure our server. For this guide, we will be using the following parameters: + +- `port`: The port on which we want to run our server +- `graphiql`: Whether we want to enable the GraphiQL interface or not + +Additionally, there's also an [`@upstream` operator](../operators/upstream.md) that allows us to specify various HTTP parameters that we would like to use in our schema. We will be using this operator to set up our HTTP client's configuration. We would be using the following parameters: + +- `baseURL`: The base URL of the API that we want to interact with +- `timeout`: The timeout for our HTTP requests + +All together, our schema would look like this: + +```graphql showLineNumbers +schema @server(port: 8000, graphiql: true) @upstream(baseURL: "https://api.spotify.com/v1", timeout: 60) { + query: Query +} +``` + +**Note that we don't have a `mutation` field in our schema. We will come to mutations later on, and hence, it's left blank for the moment.** + +### [Get an album's details](https://developer.spotify.com/documentation/web-api/reference/albums/get-album/) + +We will use this endpoint to get the details of an album. We will be using the `@http` operator to make a `GET` request to the endpoint. The `@http` operator takes in the following arguments: + +- `id`: The id of the album +- `accessToken`: The access token that we obtained earlier + +```graphql showLineNumbers +getAlbum(id: String!, access_token: String!): Album + @http( + method: "GET" + path: "/albums/{{args.id}}" + headers: [{key: "Authorization", value: "Bearer {{args.accessToken}}"}] + ) +``` + +As you can see, we are passing the `id` and `access_token` to the `@http` operator via our schema's arguments. `@http` internally uses **Mustache Templates** to render the values of the arguments ar runtime. + +Next up, we will be starting our server and testing this query. To start the server, run the following command: + +```bash +tailcall start spotify.graphql +``` + +Once the server has started, you can open the GraphQL Playground at `http://localhost:8000` and run the following query: + +```graphql showLineNumbers +getAlbum(id: "1OSzM1OWqtTnmIJJQpn62Q", accessToken: "") { + id, + name, + tracks { + items { + id + name + href + } + total + } + images { + url + }, + artists{ + name + } + } +``` + +The id `1OSzM1OWqtTnmIJJQpn62Q` is for [Charlie Puth's Nine Track Mind](https://open.spotify.com/album/1OSzM1OWqtTnmIJJQpn62Q?autoplay=true). You can replace it with any album ID that you like. + +Running this query will return the following response: + +```json showLineNumbers +{ + "data": { + "getAlbum": { + "id": "1OSzM1OWqtTnmIJJQpn62Q", + "name": "Nine Track Mind (Deluxe Edition)", + "tracks": { + "items": [ + { + "id": "19f9roe77Hen23e6vJ1iBN", + "name": "One Call Away", + "href": "https://api.spotify.com/v1/tracks/19f9roe77Hen23e6vJ1iBN" + }, + { + "id": "0DbVHIAldoWzrImJU9WN5y", + "name": "Dangerously", + "href": "https://api.spotify.com/v1/tracks/0DbVHIAldoWzrImJU9WN5y" + } + //... + ], + "total": 15 + }, + "images": [ + { + "url": "https://i.scdn.co/image/ab67616d0000b27379ea6a5f1580c0e5aded18e9" + }, + { + "url": "https://i.scdn.co/image/ab67616d00001e0279ea6a5f1580c0e5aded18e9" + }, + { + "url": "https://i.scdn.co/image/ab67616d0000485179ea6a5f1580c0e5aded18e9" + } + ], + "artists": [ + { + "name": "Charlie Puth" + } + ] + } + } +} +``` + +### [Getting tracks of an album](https://developer.spotify.com/documentation/web-api/reference/get-an-albums-tracks) + +Yet another example of fetching, this time, we will shift our focus on how we can handle pagination with Tailcall. The `tracks` field of the `Album` type returns a `TrackPage` type. This type is an implementation of the `Page` interface. The `Page` interface is implemented by both `TrackPage` and `AlbumPage` types. This allows us to use the `Page` interface as a return type for our `tracks` field. This is how the query schema looks like: + +```graphql showLineNumbers + getTracksOfAlbum(id: String!, limit: Int!, offset: Int!, accessToken: String!): TrackPage + @http( + method: "GET" + path: "/albums/{{args.id}}/tracks?limit={{args.limit}}&offset={{args.offset}}" + headers: [{ key: "Authorization", value: "Bearer {{args.accessToken}}" }] + ) +``` + +As you can see, we are passing the `limit` and `offset` arguments to the `@http` operator. This allows us to control the number of items that we want to fetch and the offset from which we want to fetch the items. This is how the query looks like: + +```graphql showLineNumbers +getTracksOfAlbum( + id: "1OSzM1OWqtTnmIJJQpn62Q", + limit: 5, + offset: 0, + accessToken: "") { + total, + items { + name + } +} +``` + +Here, we are fetching 5 items (`limit=5`) per page, and we are starting with the very first (`offset=0`) element in the database. + +The response to this query looks like this: + +```json showLineNumbers +{ + "data": { + "getTracksOfAlbum": { + "total": 15, + "items": [ + { + "name": "One Call Away" + }, + { + "name": "Dangerously" + }, + { + "name": "Marvin Gaye (feat. Meghan Trainor)" + }, + { + "name": "Losing My Mind" + }, + { + "name": "We Don't Talk Anymore (feat. Selena Gomez)" + } + ] + } + } +} +``` + +As a rule, whenever you are fetching a list of items, or any data that is large, **Caching** is a must. This is where the [`@cache` operator](../operators/cache.md) comes into play. The `@cache` operator allows us to cache the response of a query for a certain amount of time. This is how we can use the `@cache` operator in our schema: + +```graphql showLineNumbers +getTracksOfAlbum(id: String!, limit: Int!, offset: Int!, accessToken: String!): TrackPage + @http( + method: "GET" + path: "/albums/{{args.id}}/tracks?limit={{args.limit}}&offset={{args.offset}}" + headers: [{ key: "Authorization", value: "Bearer {{args.accessToken}}" }] + ) + @cache(maxAge: 3000) +``` + +This will now cache the response of the request for `3000ms`. + +### [Save albums for current user](https://developer.spotify.com/documentation/web-api/reference/save-albums-user) + +This endpoint will demonstrate you how to use the `@http` operator to make a `PUT` request. Note that this block will go inside `type Mutation`, so do make sure that you have added `mutation: Mutation` in the schema block at the very beginning. This is how the mutation schema looks like: + +```graphql showLineNumbers +saveAlbum(input: AlbumInput, accessToken: String): String + @http( + method: "PUT" + path: "/me/albums" + encoding: ApplicationJson + headers: [{ key: "Authorization", value: "Bearer {{args.accessToken}}" }] + body: "{{args.input}}" + ) +``` + +This mutation doesn't return anything, so we have set the return type to `String`. Notice that there are two new fields in the `@http` operator: + +- `encoding`: The encoding of the request body. This can be either `ApplicationJson` or `ApplicationFormUrlEncoded`. The default is `ApplicationJson`. +- `body`: The body of the request. This is a Mustache template, and hence, we can pass in the `input` argument directly. + +Once done, we can go to the playground and issue the mutation like so: + +```graphql showLineNumbers +mutation { + saveAlbum(input: {ids: ["1OSzM1OWqtTnmIJJQpn62Q"]}, accessToken: "") +} +``` + +Doing this will add the album to your saved albums. + +### [Get a user's saved albums](https://developer.spotify.com/documentation/web-api/reference/get-users-saved-albums) + +Using this, we can get a list of all the albums that a user has saved. This is how the query schema looks like: + +```graphql showLineNumbers +getSavedAlbums(limit: Int!, offset: Int!, accessToken: String!): AlbumPage + @http( + method: "GET" + path: "/me/albums?limit={{args.limit}}&offset={{args.offset}}" + headers: [{ key: "Authorization", value: "Bearer {{args.accessToken}}" }] + ) +``` + +And then, we can issue the query like so: + +```graphql showLineNumbers +getSavedAlbums( + limit: 10 + offset: 0 + accessToken: "" +) { + total, + items { + name, + artists { + name + } + } +} +``` + +Note that, this response can have varying results depending on your saved albums. + +### [Remove an album from a user's saved albums](https://developer.spotify.com/documentation/web-api/reference/remove-albums-user) + +Let's remove the album that we added earlier. This is how the mutation schema looks like: + +```graphql showLineNumbers +removeAlbum(input: AlbumInput, accessToken: String): String + @http( + method: "DELETE" + path: "/me/albums" + encoding: ApplicationJson + headers: [{ key: "Authorization", value: "Bearer {{args.accessToken}}" }] + body: "{{args.input}}" + ) +``` + +We call it using the following mutation: + +```graphql showLineNumbers +mutation { + removeAlbum(input: {ids: ["1OSzM1OWqtTnmIJJQpn62Q"]}, accessToken: "") +} +``` + +That will remove the album from your saved albums. + +## Conclusion + +This guide demonstrated how you can use Tailcall's `@http` operator to interact with the Spotify API. We also saw how we can use the `@cache` operator to cache the response of a query. Along with that, we also used `@upstream` and `@server` operators to set up our server and configure our HTTP client.