Part 2 - The Attack of the Spotify Integration
Welcome back, and if you're a glutton for punishment I'm glad (maybe?) you didn't leave. We're now going to start with the second part of our Old School Shuffle app, which is the integration with Spotify.
We're starting again with a brainstorm on a piece of paper, but this time we'll focus on back-end first and deal with front-end components at the end. What do we need to be able to do with Spotify?
The Spotify Plan
Registering your app with Spotify
Spotify has a really all-start API that is extremely well documented and they handhold developers through it with their Web API Tutorial, but we'll try and get started quickly. First you have to have a Spotify listener account, which you probably have if you're here. After that, you'll need to go to your Dashboard to register as a developer and also register your app by getting a Client Id.
The dashboard looks like this after you log in:
They walk you through registering the app and getting a Client Id. If you're making a non-commercial app (an open-source app like this) it's free, but I think they charge you for commercial access. When you're done you get a Client Id on your dashboard.
We're going to need that Client Id (I blurred mine), so go ahead and save it in your .env file as REACT_APP_SPOTIFY_CLIENT_ID. If you didn't do it as part of setup, we also need to set up a "Redirect URI" for our app. Click the Edit Settings button in your dashboard and you should see a pop-up like this:
You will need to add both your development environment (should be http://localhost:3000 if you're using create-react-app) as well as your production environment URL when we get to that later. Go ahead and save that in your .env file too as REACT_APP_SPOTIFY_DEVELOPMENT_REDIRECT_URI and REACT_APP_SPOTIFY_PRODUCTION_REDIRECT_URI. You can check your production URI in your Cosmic JS dashboard.
When we send our login request Spotify will use that Redirect URI to make sure it's really us. Now your .env file should look kinda like this:
While we're getting things set up, let's go ahead and install spotify-web-api-js with npm, as well as some specific Lodash helper functions we'll be using.
Let's also create a spotifyFunctions.js file in the src directory (next to cosmicFunctions.js). It's gonna have a LOT of code in it though, so let's just import our libraries for now and then talk about how Spotify's authentication works.
Authorization is one of those things that every web developer dreads.You have to do it all the time, but it tends to be a minefield of Cross-Origin (CORS) errors, OAuth2 servers, Json Web Tokens (JWT), handshake tokens, refresh tokens, and Authorization HTTP headers.
All of these are doable, but they make me want to throw up a little bit, especially on a small demo application. Spotify supports 3 different authentication methods but we're fortunate that they offer an Implicit Grant flow. It's not very secure, but we're talking about music playlists not bank accounts, and it's only good for one hour so you can only cause too much trouble. It's perfect for us because it doesn't require any server-side code. That's handy because create-react-app obscures the express routes by default. You can set up a proxy server with it, but that seems overkill for just this authentication.
Here's Spotify's diagram of how Implicit Grant works.
Unlike OAuth/OAuth2 or JWT that require ajax requests, Implicit Grant is super simple and works off of a query string, which is a way to pass data in the URL of a webpage. In effect, what we do is make our login button an external link to Spotify's authorization URL, including data like our Spotify Client ID in a query string. By following the link the user actually leaves our page, and goes to a Spotify-hosted page to give our app access permissions.
If they agree (or if they're already logged in to Spotify and gave us permissions earlier) Spotify will examine the data against the app we registered in the database and then they will redirect the user back to our page, but including a query string with an authorization token. All we have to do to access the API is include that token (BQAHoHp... in the screenshot) in our API requests. We actually don't even need to do authorization headers because spotify-web-api-js will do it for us.
URL before log in
URL after log in
Let's try it out now by building 2 of our 3 Spotify-related components:
is an outer wrapper for our Spotify-related UI
is what we'll show when the User isn't logged into Spotify yet. Basically just a login button.
lets the user choose from their exist playlists and shuffle them by album. We'll tackle this one later.
We'll also need to update by replacing our dummy "Spotify Player Here" with . And we're going to add a few functions to spotifyFunctions.js. Here's the code.
We won't spend much time on these components: in we replaced our dummy Spotify placeholder text with . has state for whether we are logged in and, if so, what our access token from Spotify is. If state.loggedInToSpotify is false then we show the component (basically just a log in button), or shows a placeholder with the access token.
also has a componentDidMount lifecycle method that checks at each render whether we are logged in using a function from our spotifyFunctions.js library. Here's that code too.
Let's start with logging in. redirectUrlToSpotifyForLogin is just a string concatenator to build a URL with query string to tell Spotify who our app is and what permissions (Spotify calls them scopes) we want to ask the user for. It also includes our Client Id and Redirect URI that we registered with Spotify. You can see in that we simply link our login button to this generated URL.
checkUrlForSpotifyAccessToken is also very simple. It simply checks the URL for a query string with a parameter called access_token. This works because when we first initialize our app our URL will be http://localhost:3000, but after we log in, Spotify will redirect to our app using our Redirect URI but with their authorized query string attached. It'll look something like http://localhost:3000/#access_token=whatever. At that point will be rerendered again, see the access token, and render the access code in text instead of the connect to Spotify button. Easy peasy!
FYI getHashParams is just a generic helper function to parse query strings from the URL.
Now we have everything in place we need to log in to Spotify. Here's what it should look like.
Before log in
After Log in
The Spotify UI
So, now that we're logged in we have to add and wire it up. The user should be able to choose from a list of their playlists and produce a new version shuffled by album. In case your playlist has SINGLES on it (heretic!) we'll also include an option to automatically add in the whole album. Here's what it should look like:
You're an old hand by now at the front-end React code, so I'm just going to give you the code all at once instead of spoon feeding. If you have questions about it you can ask me in the comments. is heavy on Material UI, especially their Menu component, and this tutorial can help you make sense of that if you get turned around. Don't forget about wiring it into .
You'll notice a lot of references to our spotifyFunctions.js helper library in there, so it won't render until we tackle that part. That is where I want to focus next.
Meet Mr. Spot: A iffy API
As a quick recap, here's what we want the API part of the app to do:
The Spotify Plan
Here's all the code. Get an overall feel for how it matches up with our requirements, but we're going to go through each function so don't worry if you don't understand everything. I apologize in advance that it could probably stand a little refactoring.
Alright - let's take it from the top!
We addressed the imports from a little bit ago, but we need an actual instance of the spotify-web-api-js wrapper library to use it, so we're creating that here and calling it spotifyApi.
These are the authentication functions we addressed earlier. Nothing more to say here.
Once we have our access token we need to pass it to our spotifyApi instance, and then we don't have to worry about remembering it for later function calls. We have to export this function because it is called in componentDidMount in .
Once we have the access token we want to get an array of the user's playlists that they can choose from. Like all functions that call 3rd-party APIs, this has to be an async function. Our spotifyApi has a built-in getUserPlaylists method but Spotify gives us lots of information about the playlist that we don't need.
To thin that down we can map through the array of playlist objects we get from the API and return an array of simpler objects with just the id and name of each playlist using ES6 destructuring. We can feed that to state.playlists in and use it to populate our menu.
Notice we also have an error handler that returns "Can't Download your Playlists" if it can't connect to the API. That feeds into the menu too, and so the user gets a somewhat helpful message instead of a crashed app if there are connection issues.
Once a user chooses a playlist we need to find out what tracks are on that playlist. We need the name and id of the track, the name and id of the album it's on, what track number it is on that album, and the name and id of the artist that sings that song. Spotify also saves a URI we can just drop into a browser to get the track, so we'll save that too.
This is very similar to getSimplePlaylistTracks, but instead of receiving a playlistId we pass it an albumId, albumName and albumUri. This is helpful for when the user wants to add discography because we can loop through their playlists, get the albums for each track, and then use this to get all the other tracks from the same album.
Both functions look very similar, but are subtly different because the Spotify API responses for albums are structured slightly differently than their data on playlists. There's probably there is a clever way to refactor them into one function - if you come up with it let me know!
Just like we said in the last section, this function receives the output of getSimplePlaylistTracks and produces an array of albumIds from the tracks in that playlists. That can be fed into getSimpleAlbumTracks if we need the full discography or just return the albumId if we just need to sort a playlist by album. The returnSimpleArray flag lets us decide which kind of output we want.
Notice that we get the array of albumIds first, then remove duplicates from the array using the Lodash uniq function, then shuffle it. It's more efficient to shuffle the albums first before we add tracks to them.
This is just a general helper function to shuffle arrays randomly.
This is basically the same as identifyAlbumsInPlaylist, except it uses the artistId. We'll use this for playing the discography of artists profiled in . Once again we could probably refactor these two functions together.
This is a tricky one. Essentially we're taking a large array of tracks in a playlist and converting it from a cohesive array to an object with smaller arrays with the same albumId. Those short arrays are albums, so then within each album we sort tracks by their track number on the album. Later we can recombine them.
Here's an example of what the effect of convertPlaylistToObjectByProperty looks like:
Cool. Here's a quick one:
This is a great helper function to sort and array of objects based on a specified property of the objects within it when you feed it as a parameter to array.sort like we did in convertPlaylistToPbjectByProperty . I unabashedly lifted this from Stack Overflow, and owe Ege Ozcan a debt of gratitude!
Look at that export statement! Look at how readable that is! This function is called from the Play Now button in when a user chooses to shuffle a playlist by album WITHOUT adding missing tracks. It is an async function, and we're really benefiting now from abstracting out our lower-level operations.
We start by receiving the state from and pull out the name and id of the playlist chosen by the user. From there we:
get an array of tracks in the playlist
get an array albums in the playlist, which has already been shuffled
use convertPlaylistToObjectByProperty to sort the big array of tracks into an object with smaller arrays sorted by album
recombine the smaller (now sorted) track arrays back into a big array of tracks, but now grouped by album
flatten that recombined array so now it's a simple playlist again
pass that flat, recombined array to the createPlaylist function
I know we haven't tackled createPlaylist yet - saving that for last.
This one is very similar to our last function, but byAlbumWithDiscography has different logic in the middle. Once we have the shuffled array of albums from the user's playlist we throw the actual playlist away. Instead we loop through the array of albumIds and do an API call to get the tracks for each one, which we then combine into one big playlist.
There's one slightly unusual code pattern in there:
After Promise.all resolves we have an array of arrays of tracks, so we flatten it just like before and pass the result to createPlaylist.
This is the function we use to create a playlist of the entire discography of the artist displayed in . It is almost exactly the same as byAlbumWithDiscography except instead of getting the array of albumIds from a playlist we get it by asking the SpotifyAPI for albums associated with the artist. While we're at it, let's go ahead and wire up this function with the Play Discography button in :
The final spotifyFunction - createPlaylist
Here it is ladies and gents - the last function in spotofyFunctions.js and the last code of the project! Ready to create a playlist and play it?
Well, Spotify's API doesn't make it easy on us. Even though we have the seemingly handy spotifyApi.createPlaylist and a spotifyApi.play functions, they come with some conditions:
We have to get the UserId before we can create a playlist
We have to create a blank playlist first, then add tracks to it.
We can't add more than 100 tracks at a time, so if we have more than 100 we need to call spotifyApi.addTracksToPlaylist multiple times.
We can only play the playlist if the user has an "active device" but it's not clear what an "active device" really means.
No matter! That's why we have this custom createPlaylist function to handle the complexity. The logic of the function basically follows the challenges we just listed. chunk is a great Lodash library that splits a large array into smaller arrays. Also note that we have to use the Promise.all construction again for adding tracks because it's an async operation inside of an array.map.
Whoops - Lessons from real life - API setback
I know, you were so excited to be done, and I was too. But Spotify had other plans. Long story short between when I started this project and when the blogs were publish Spotify announced changes to their API and then reneged on those changes, thereby breaking our app! All they did was change the endpoint for creating a playlist, but that means that our spotofy-web-api-js library doesn't work! If I were a better citizen I'd do a pull request on the library to fix it, but the quick fix is to write our out temporary ajax API call for creating playlists for now instead of the library. Once the library is updated we'll change it back.
I didn't copy the whole file to the gist since we're only changing a few things:
- Now that we need our access token outside of the spotify-web-api-js library we have to add a global variable to hold it, as well as update setAccessToken
- We need to call our tempCreatePlayist function inside our createPlaylist function that we wrote above.
- We need to make our temporary function, which is just an ajax call to the endpoint we need.
Check it out - you should now be able to login to Spotify, choose among your playlists, and shuffle by album. You can also add related discography, as well as generate by-album discography for Kacey Musgraves, Ryan Bingham and Ted Leo (which I strongly recommend listening to in their entirety) or add homages to your own favorite artists!
The buttons don't do anything visual when they are clicked, but if you have your phone open to your playlists page next to you it should start playing after a few seconds, otherwise check your playlists to see the new album shuffled method and start listening old school style!
Deploying the create-react-app to production
Last step, should you so desire, is to deploy your app so you can use it on the wild internet instead of just with your local development environment. This is not difficult, but the process isn't well documented when using create-react-app like we are.
Because we just have those files, that means that we need to either put them on an existing server or spin up a simple Express server in Node to serve those files for us. That's the route we're going to take, so add a new file in your root directory called app.js. This is a DIFFERENT FILE than App.js in your components folder, and we'll write up a simple server to serve those build files:
Now that we have a server to host our build files we need a script to use it. Open up your package.json and let's do that.
create-react-app already has a nice build script for us, so for our productionstart script all we need to do is run that build script and then start the server. Notice we also added express as an explicit dependency. It shouldn't be necessary since create-react-app already uses it on the back end, but nice to be tidy.
The last file we need to add is Procfile (capitalized, no dot) which contains instructions for the deployment service (originally Heroku, but we'll use Cosmic JS' service). The Procfile will tell the service to spin up a single web node and to run our productionstart script.
So close we can taste it! Cosmic JS makes it extremely easy to deploy straight from your Github repo (you have been uploading your git commits, right?). All you have to do is go back to you Cosmic JS Dashboard and there is a Web Deployment option underneath settings. From there you simply click the blue Deploy to Cosmic JS button. It will take a few minutes to get set up and send you an email when you production Old School Shuffle is live.
The very, very, very last thing now is that we need to set our environmental variables within our Cosmic JS deployment to mirror our .env file from development. Protip - don't forget to click the Set Environment Variables button, and then give it a few minutes to update.
Now that our app actually works I love it a little more, so I classed up the CSS to give it a little more pizzaz. These minor changes aren't shown in the github gists but they are reflected in the repo.
That's all folks!
And now enjoy the fruits of your labor! Thanks for joining me, and I hope you enjoyed it and learned something. This is my inaugural coding tutorial so please leave a comment if I missed anything... or if you have any comments!