Connecting to Spotify's API
Diego Andriano (not AI)
24-05-2023
1639 / 250wpm = 6.5m (with code + expanded)
Introduction
Hello everyone! In the beautiful summer of 2022 (I live in Argentina, so you're gonna have to picture a sunny December) I was looking at the job market with a single purpose: Getting a new job.
This was no easy fit for me. At this stage I had worked in the same place for over ten years. So, with more courage than game, i started applying everywhere (or actually answer the recruiters positively instead of giving them the old "I'm not looking for a change right now, thanks anyways").
One company had a task as a technical requirement for passing the interview. This web book is about the process of delivering said task, and the result is the small project we're about to see.
What we're making
We're creating a simple API abstraction. Using Spotify's api, we're going to get all albums from a given artist's name.
I call it an abstraction because we're actually just returning whatever spotify gives us when we hit their endpoint, the only difference on doing it directly to Spotify's API should be that you don't have to place any credentials, just hit our endpoint with a band name and you'll get what you're looking for.
To see the result, you can play with this URL.
Tools
There is not much needed for what we're going to accomplish.
- Laravel
- Spotify account
- Postman account
Starting point and pre-steps
First off, we need to create our very own Spotify app, in order to get our credentials. Head over to Spotify's Dashboard, log in and create your app. Once done, you'll get access to your client_id and your client_secret. They should look like this
Client ID 1234ab12abc123mnop6b11234eb8ff84 Client Secret 45abc12a1o995bb5bn123456789a4b3b
That's all we need from Spotify right now. Save those credentials as we're going to use them later.
So... what now? As we gotta start somewhere, let's head over to the documentation and try to understand the flow they want us to follow.
According to it, first of all we need to send a POST to https://accounts.spotify.com/api/token the authorization token using our client_secret from before and set the param grant_type to client_credentials and some Content-Type headers. Cool. Let's check it out on Postman.
Sure thing! As you can't just walk into Mordor, you also can't just use Spotify's API. There's a flow to it. The reasons may vary: from security, to "come on man, don't exploit us". In this case, Spotify uses something called "oAuth". 2. Oauth2. This implies tokens will be involved.
Spotify says: okay man, you can use our api. But first, you need to create a spotify app, on which we'll give you credentials. Use them to use us. But what do this credentials do? This credentials are what gives us a token. This token generally expires in a given time, this time depends on whatever the devs consider okay. This token is what we'll be using to reach the good stuff.
If this didn't solve your doubts, feel free to @ me on twitter.
On Postman
Overview
For those unaware of postman, it's just a tool to check APIs in a simple way. It's kinda like curl, for the linux lovers out there.
So, let's get into it. Or not, your call. Whatever.
Just kidding, stay with me. Please. So, get to Postman, sign up or sign in, go to your workspace and reach the next screen. Don't worry, you got this.
We need to POST, not GET. So, set the selector beside the URL field to POST. After that, let's paste the aforementioned URL:
https://accounts.spotify.com/api/token
Getting the token
Now that that's done, we gotta figure out what to do next. We know we have to send some credentials, but... how? Well, in the documentation, in the client credentials flow section, they give us the answer. We need a base 64 encoded of the client id appended to the client secret, separated by a ":", kinda like this:
# base64(client_id:client_secret) # the result will look something like this: Mjk0NGZmODZ1237895NhY2JhNmIxNDU4N2ViOGZmODQ656712378NWI0ZTc3NGZmOGFjMDzZxXcCaAsDqQwWE3YTRiM2I=
We can reach that using any base64 encoder, in this case we'll use this one, but really it's all the same.
So, back to postman, go to the headers tab right below the url box, and type "Authorization" in the key section, and "Basic " in the value section. After that, paste your base64 result. Add "Content-Type" to the key and "application/json", to the value, and that will be it.
In the body section, select "x-www-from-urlencoded" and add "grant_type" in the keys, and "client_credentials" in the value. You'll get something like this:
{ "access_token": "BQCDP-T3iviHYQH6BkYLDIXu0hmZxaot39o0IW7t_0O5UF3HWWwaKXOpK_Z6mz6Eq3wsJd0ssSsxXQUSBu23mzx6LITR-ju85VvdREk4aT0y6_yp1XI", "token_type": "Bearer", "expires_in": 3600 }
Getting the artist's ID
Hooray! We got our token. Open a new tab in your postman's workspace, and let's try to get our albums.
In the docs we'll discover a new URL: "https://api.spotify.com/v1". This is our base url.
As we're trying to get albums of given artist, we need to hit this endpoint: /search?q=bandName&type=artist".
I'll try to get Los Piojos, so I'll replace "bandName" with "Los+Piojos". This endpoint requires GET, be sure you set that up in order for it to work properly. Set the headers as: "Authorization" => "Bearer YOUR ACCESS TOKEN", "Content-Type" => "application/json". This will give us something that looks like this:
{ "artists": { "href": "https://api.spotify.com/v1/search?query=Los+Piojos&type=artist&offset=0&limit=20", "items": [ { "external_urls": { "spotify": "https://open.spotify.com/artist/0SnyKkoyBaB2fG8IJH4xmU" }, [...]
You'll notice we don't have our albums yet. We needed to hit this endpoint first to get our band's id. And we did!
"id": "0SnyKkoyBaB2fG8IJH4xmU"
Remember your access token may expire!
Getting the artist's albums
For the albums, this is the endpoint: /artists/$id/albums. I'll skip how, as at this point you probably already can figure it out. Just remember to open a new tab as to not lose the artist's ID. Don't close the current one. And... et voila!
{ "href": "https://api.spotify.com/v1/artists/0SnyKkoyBaB2fG8IJH4xmU/albums?offset=0&limit=20&include_groups=album,single,compilation,appears_on", "items": [ { "album_group": "album", "album_type": "album", "artists": [ { "external_urls": { [...]
If you liked the content, i'd appreciate if you subscribed.
On Laravel and code review
'Kay. So, we got our stuff on Postman, but what about Laravel? Thinking about how to go through the explanation, I figured it would be much more benefitial to everyone if I just give the repo and answer some possible questions you might have upon reviewing it.
You're looking at basic Laravel app. Find routes in routes/api.php. Spotify gateway and what we went through in postman in app/Http/Gateway/Spotify.php. You can look at SpotifyRequest.php to see needed parameters for the URL. You may find tests in tests folder (redundant? I think not!)
Well, I uploaded my code to github, right? and credentials are supposed to be secret, right? if I just placed my precious creds in the spoify class, I might reveal sensitive data. So, the answer is to modify the .env.example to accept those params (.env.example might eventually be promoted to .env), so the person that uses the code knows what to do to make it work.
Laravel gives this answer for me:
If you execute the config:cache command during your deployment process, you should be sure that you are only calling the env function from within your configuration files. Once the configuration has been cached, the .env file will not be loaded; therefore, the env function will only return external, system level environment variables.
We need asForm() so our http post is sent with the "x-www-from-urlencoded" header, else the grant-type won't be accepted correctly as post data.
To be honest, I was only familiarized with use cases when I started this API, and I think I used them quite similarly to services. It can be improved, though. Here's a good article about the subject.
I figured Models would be waaay overkill. I just needed a class with some properties and maybe a method. DTO's (Data Transfer Objects) could be thought of as data objects, so I went for it.
On another note (even though I haven't used methods on my DTO I mentioned it before), some people will say a DTO can't have methods, some will say oh god no, please no. Some will say they may. I'll say "meh, it's a small API".
No.
Come back, I was kidding! I did not go for TDD for this project (meaning the testes (lol, actual misspell, I just went for it and left it hanging there) were made after the fact), and used PHPUnit.
Inside "Fakes" folder, you make find SpotifyFake.php, which has some methods that return some results the real gateway would. The reason for this is to avoid having to call the real API whenever we want to run our tests. Maybe I could've placed the json from the getAlbums method inside it's own file. If I were to do that, I'd place it inside storage/app/testing/fakeAlbums.json. The json is a real-modified json from the API that i took from one of the requests.
Inside "Features" folder, there's the AlbumControllerTest. The first thing it does is swapping the real gateway with the fake one. This is done using Laravel's service provider. Everything else is just regular testing.
Last words.
First off, If you have any questions, feel free to @ me on twitter. This was a simple and small API made in Laravel. But it was definitely not my first version of it. Maybe if it were, I'd have gotten the job. Hope you found it useful, and maybe even fun to read. Have a good day!