Let’s Build A Full Stack App With The Spotify Algorithm and API
By: Josh Streger
Tags: Development, Spotify, JavaScript, Programming, Web Development

Photo by Heidi Fin on Unsplash

First of all, Spotify, if you are reading this, please hire me. Okay now that we have that out of the way…

Unbeknownst to many people, Spotify has a non-advertised feature of immense depth. So deep that the feature itself could be its own application. And it all exists in one little endpoint on Spotify’s public web api:

https://api.spotify.com/v1/recommendations

Perhaps if you’re recently getting into web development, you’ve stumbled upon Spotify’s public Web API to play with during your practice. With this API you can authenticate using your premium account, retrieve all of the data for your user’s liked songs and playlists, and even play music through your browser with their Web Player SDK.

But this recommendations endpoint is something else entirely. This endpoint allows you to tap into Spotify’s algorithm for recommending you songs. If you use Spotify, you’ve most likely listened to your personal curated playlists at least once. These include playlists such as Discover Weekly, Release Radar, or any of the Daily Mix series. With a little bit of magic, we can make our own tweaks to the recommendation algorithm and make our own autogenerated playlists to our liking.

Here’s how it works

As per Spotify’s web api:

Recommendations are generated based on the available information for a given seed entity and matched against similar artists and tracks. If there is sufficient information about the provided seeds, a list of tracks will be returned together with pool size details.

So all we need to provide is any number of “seed” songs, artists or genres, and we can get back a list of recommended tracks.

Let’s say I wanted to get a recommended playlist (or just 1 song in this case) that was based on the seed genre “electronic”. I then authenticate with Spotify, and send a get request like this to the recommendations endpoint:

"https://api.spotify.com/v1/recommendations?limit=1&market=US&seed_genres=electronic"

I would receive a response from their server with this (i removed a lot of fluff data for viewing ease):

{

"tracks": [

{

"artists": [

"id": "25fqWEebq6PoiGQIHIrdtv",

"name": "Baauer",

"type": "artist",

"uri": "spotify:artist:25fqWEebq6PoiGQIHIrdtv"

}

],

"name": "Harlem Shake",

"track_number": 1,

"type": "track",

"uri": "spotify:track:01XFgRZfZI7oBagNf1Loml"

},

],

"seeds": [

{

"initialPoolSize": 1000,

"afterFilteringSize": 1000,

"afterRelinkingSize": 972,

"id": "electronic",

"type": "GENRE",

"href": null

}

]

}

In this case I only searched for 1 song, using the genre seed electronic . We have to ability to influence seeds with many more fields:

Limit: number of songs coming back (min 1, max 100)

seed_artists: comma separated list of seed artists (need to use their spotify ID for this)

seed_genres: comma separated list of any genres in the set of available genre seeds https://api.spotify.com/v1/recommendations/available-genre-seeds

seed_tracks: comma separated list of SpotifyIDs for a seed track

and then we have the real interesting stuff:

max_*: The max value of ______

min_*: The min value of _______

target_*: the target value of _____

The wildcard in these query parameters refer to the following list of tune-able track attributes:

acousticness: a confidence measure from 0.0 to 1.0 on whether the track is acoustic.

danceability: how suitable a track is for dancing based on a combination of musical elements such as tempo, ehythm stability, beat strength, and overall regularity.

duration_ms: the duration of the track in milliseconds

instrumentalness: Predicts whether a track contains no vocals.

key: The key that the track is in. 0=C, 1=C#, 2=D and so on

liveness: detects the presence of an audience in the recording.

loudness: the overall loudness of a track in decibels.

mode: the Modality (major/minor) value of the track. Major is 1, Minor is 0.

popularity: The popularity of the track. The value will be between 0 and 100, with 100 being the most popular.

speechiness: Detects the presence of spoken words in a track.

tempo: the overall estimated tempo of a track in beats per minute (bpm).

time_signature: An estimated overall time signature of the track.

valence: Describes the musical positivity conveyed by the track. Higher valence = more positive vibes.

More in depth detailing about these specific values can be read on this page.

Pretty damn awesome

The ability to use this endpoint is oh so cool. There are an array of very interesting projects we can make, but one of the ones I’ve always wanted to make was a playlist generator. So that’s exactly what we are going to make, with a react frontend and an express backend. Let’s get started.

My plans with this series

Ideally what I would like to do, is go over full stack tasks in a series, like the following outline:

Part 1: Setting up the backend

Part 2: Creating frontend components & functionality

Part 3: Creating Tests for Backend + Frontend Logic

Part 4: Deploy to a production server

Making the server

I’m not going to go through all of the logistics with the backend server, mainly just the authentication pieces with Spotify. If you are interested in learning about how I am maintaining a secure session, please check out this other article I recently wrote, called How to Create Secure React & Express Apps:

$ npm init (for creating a package.json, and checking my entry point to be server.js) And now we create our backend files $ touch server.js

$ touch .env Now for our backend dependencies $ yarn add express cookie-session helmet hpp csurf dotenv axios jsonwebtoken

and here is the contents of our server.js

// server.js

const express = require('express');

const session = require('cookie-session');

const helmet = require('helmet');

const hpp = require('hpp');

const csurf = require('csurf');

const dotenv = require('dotenv');

const path = require('path');





/* Import config */

dotenv.config({path: path.resolve(__dirname, '.env')});



/* Create Express App */

const app = express();



/* Set Security Configs */

app.use(helmet());

app.use(hpp());



/* Set Cookie Settings */

app.use(

session({

name: 'session',

secret: process.env.COOKIE_SECRET,

expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours

})

);

app.use(csurf());





app.listen(8080, () => {

console.log("I'm listening!");

});



module.exports = app;

At the moment, your .env file only needs one value, which is the secret we are using to sign our session cookie:

# .env

COOKIE_SECRET=asecretyoushallputhere

Running your app should show the following output:

$ yarn start

yarn run v1.22.4

$ node server.js

I'm listening!

Registering our app with Spotify

Go to https://developer.spotify.com/dashboard/login After logging in you can create an app with Spotify like so:

3. After hitting create, we are redirected to the dashboard of our application “Recommendations”

4. Let’s hit “edit settings” and add http://localhost:3000/auth/callback to the Redirect URIs field. This will be the link that Spotify sends the browser back to after it has authenticated with Spotify.

5. Save your settings and take a look at the dashboard, we are going to need two pieces of info from the page, the ClientID and the ClientSecret. Click “Show client secret” to view and copy it. Let’s put these in our .env file:

# .env

COOKIE_SECRET=yoursecretcookiekey

SPOTIFY_CLIENT_ID=<your client id here>

SPOTIFY_CLIENT_SECRET=<your client secret here>

Making the authentication routes

Spotify actually offers a few different ways of authentication with apps. One of these is the Authorization Code Flow. Similar to the Auth0 flow I used in my other article the flow goes as follows, this is a graphic provided by Spotify:

To simplify:

Have our application request authorization, and then have the user log in via spotify’s auth flow:

// routes/auth.js

const express = require('express');

const querystring = require('querystring');

const axios = require('axios');

const jwt = require('jsonwebtoken');





const router = express.Router();



router.get('/login', (req, res) => {

res.redirect(`https://accounts.spotify.com/authorize?${querystring.stringify({

response_type: 'code',

client_id: process.env.SPOTIFY_CLIENT_ID,

redirect_uri: process.env.SPOTIFY_REDIRECT_URI,

})}`);

});

The login endpoint is where we redirect the user to spotify’s authorization page, passing in the client_id, redirect_uri, and response type as query parameters).

2. After we receive our code back from our authentication, we can use that to get an access and refresh token. The access token is what we need to get data from the Spotify Web API, such as searching for tracks and using the recommendations endpoint

router.get('/callback', async (req, res) => {

const {code} = req.query;

const clientId = process.env.SPOTIFY_CLIENT_ID;

const secret = process.env.SPOTIFY_CLIENT_SECRET;

const redirect_uri = process.env.SPOTIFY_REDIRECT_URI;

const grant_type = 'authorization_code';



const basicHeader = Buffer.from(`${clientId}:${secret}`).toString('base64');

const {data} = await axios.post('https://accounts.spotify.com/api/token', querystring.stringify({

grant_type,

code,

redirect_uri,

}), {

headers: {

Authorization: `Basic ${basicHeader}`,

'Content-Type': 'application/x-www-form-urlencoded'

}

});



const sessionJWTObject = {

token: data.access_token,

};



req.session.jwt = jwt.sign(sessionJWTObject, process.env.JWT_SECRET_KEY)

return res.redirect('/');

});

This request looks a little complex, but It’s nothin too bad. Its simply taking the code that we got from the request query, and using it to send a post request to the https://acounts.spotify.com/api/token. The authorization header is Basic Auth using our clientId and secret, and the content-type is per spec for the Spotify documentation.

If we were to print out the data we get back from this, it would look something like:

{

access_token: 'longcode',

token_type: 'Bearer',

expires_in: 3600,

refresh_token: 'anotherlongcode',

scope: ''

}

We take the access token that we get from this request, and shove it into our session-cookie. If you want to know a bit more about session cookies, definitely check out the other article.

And that is pretty much it for the Spotify Authentication! We can add two other endpoints to make our lives easier for later:

router.get('/current-session', (req, res) => {

jwt.verify(req.session.jwt, process.env.JWT_SECRET_KEY, (err, decodedToken) => {

if (err || !decodedToken) {

res.send(false);

} else {

res.send(decodedToken);

}

});

})



router.get('/logout', (req, res) => {

req.session = null;

res.redirect(

`/`

);

});



module.exports = router;

/current-session : This endpoint is verifying the jwt-token that is inside of our session object, and if its valid then we send the contents to the client in the request. If it’s not there, we send back false. This will make a bit more sense once we make our frontend /logout : This is an endpoint to simply destroy our jwt token, which we will use to determine whether or not the user is authenticated with Spotify.

Stay tuned for Part 2

The backend portion of our Spotify playlist recommendation service is pretty much complete. What we need to do now is create the frontend that can perform all of the functionality we need! I hope to release this later in the week. If you want to review the code that I already typed out in this article, I am in progress of creating this project here: https://github.com/jdstregz/spotify-recommendations

Thank you!