Displaying Your Top Songs Using the Spotify API
Luigi Cruz / June 20, 2021
11 min read
One of my favorite part in my website is the Playlists page. Back in the days when Friendster was a thing, you are allowed to customize your profile page where you can change your page theme and upload your favorite song which is automatically played when someone from your connections visits it. Feeling nostalgic?
When I was building my site I wanted to bring back that feature. I wanted to display what song I'm currently listening to, as well as showing my most played songs from Spotify.
Now to accomplish this, I'd need to integrate my website with Spotify's Web API. So if you're curious, here's a tutorial on how I did it.
Registering your App
Before we can request data to the Spotify Web API, we have to first set up a Spotify developer application. This will allow our website to communicate to the Spotify Web API. Make sure that you have a subscribed account from Spotify that you can use for your requests.
- Go to your Spotify Developer Dashboard and log in.
- Click Create an App.
- Enter Application Name and Application Description and then click CREATE.
- Click Show Client Secret.
- Save your Client ID and Client Secret.
- Click Edit Settings.
- Add your website URL. // Ex.: https://luigicruz.dev
- Add http://localhost:3000/callback as a redirect URI. // Spotify will whitelist this endpoint and sends the code for authorization
All done! You now have a properly configured Spotify application and the correct credentials to make requests.
NOTE:
- Client ID is the unique identifier of your application.
- Client Secret is the key that you pass in secure calls to the Spotify Accounts and Web API services. Always store the client secret key securely; never reveal it publicly! If you suspect that the secret key has been compromised, regenerate it immediately by clicking the "SHOW CLIENT SECRET" then the "RESET" button from your app's dashboard.
Obtaining Authorization
For us to access the Spotify data and features, or to have our app fetch data from Spotify, we need to authorize our application. There are two ways to do this: App Authorization and User Authorization. We're going to use the latter.
Spotify, as well as the user, needs to grant our app the necessary permission to access and/or modify the Spotify user account data. To do this, we'll use the Authorization Code Flow.
Use a Library to Authenticate and Get Tokens
If you don't want to dive into too much details on how you can authenticate your app and get access and refresh tokens, you can use this package to do so. Make sure you update the file from the ./authorization_code/app.js with your own credentials such as your client_id
, client_secret
, redirect_uri
and scope
.
If you opt in using the package, you can skip the Authenticate your App and the Access and Refresh Tokens part.
Authenticating your App
First, we need to perform a GET
request to Spotify /authorize
endpoint together with these query parameters: client_id, response_type, redirect_uri, and scope. For example:
https://accounts.spotify.com/authorize?client_id=8383hdh9d3ggue92j9j0277he29r397dd3&response_type=code&redirect_uri=http%3A%2F%2Flocalhost:3000%2Fcallback&scope=user-read-currently-playing%20user-top-read
- Swap out your own app's
client_id
value - Set the
response_type
equal tocode
- Set the
redirect_uri
equal to:
http%3A%2F%2Flocalhost:3000%2Fcallback
- Add your scope. You can find the lists of scopes here. // Ex.: user-read-currently-playing and user-top-read
- Paste search the request from your browser or via Postman
This query performs a couple of things:
- The user is asked to authorize access within the scopes. // You will be prompted to log in to your Spotify account.
- The user is redirected back to your specified redirect_uri. // Redirected to
http://localhost:3000/callback
If everything goes well, a response code is then sent and attached to the query parameter of the redirect_uri that you have set from your app. Copy and save the code value. For example:
http://localhost:3000/callback?code=NApCCg..BkWtQ
Access and Refresh Tokens
Next, we'll need to retrieve the refresh token. You'll need to generate a Base 64 encoded string containing the client_id
and client_secret
from earlier. You can use this tool to encode it online. The format should be client_id:client_secret
. Swap out those two with your app values along with the code value that you've saved from earlier.
curl -H "Authorization: Basic <base64 encoded client_id:client_secret>" -d grant_type=authorization_code -d code=<code> -d redirect_uri=http%3A%2F%2Flocalhost:3000%2Fcallback https://accounts.spotify.com/api/token
This will return a JSON response containing an access_token
and a refresh_token
. Access tokens are deliberately set to expire after a short time, so we'll need the refresh_token
to generate new access_tokens
. Make sure you save the value of your refresh_token
.
Saving Sensitive Data to an Environment Variable
We don't want our sensitive data like the refresh_token
to be exposed when we upload our source code to a repository or a hosting platform. So we are going to store it in an environment variable. NextJS has it all set up for us using the .env.local
file. Make sure to have a read on it first before you continue. If you don't use NextJS, you can install the dotenv library on your app.
On your .env.local
file, add your own client_id, client_secret, and refresh_token.
SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=
SPOTIFY_REFRESH_TOKEN=
Requesting an Access Token
The access token is only valid for a short time so we'll be needing a new token after it expires. For that, we are going to use the refresh token.
On your project, create a lib/spotify.js
file and copy and paste the code below.
import querystring from 'querystring'
const client_id = process.env.SPOTIFY_CLIENT_ID
const client_secret = process.env.SPOTIFY_CLIENT_SECRET
const refresh_token = process.env.SPOTIFY_REFRESH_TOKEN
const basic = Buffer.from(`${client_id}:${client_secret}`).toString('base64')
const TOKEN_ENDPOINT = `https://accounts.spotify.com/api/token`
const getAccessToken = async () => {
const response = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: {
Authorization: `Basic ${basic}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: querystring.stringify({
grant_type: 'refresh_token',
refresh_token,
}),
})
return response.json()
}
Here's what is happening from the code above:
- It will try to look for client_id, client_secret, and refresh_token from the
.env.local
file, - Convert the buffer string client_id:client_secret into base64 // Ex.: SGVsbG8gV29ybGQ=,
- Perform a
POST
request to the TOKEN_ENDPOINT, - Attach the Authorization and Content-Type headers to the request,
- Use the refresh_token as a grant_type on the URL query string
- If succeeded, it will return an access token as a JSON response.
NOTE: If you don't use an environment variable on your project, you can just hard code your sensitive data on your source code. This is extremely not advisable so do it at your own risk.
Requesting Top Tracks Data from Spotify
We're going to use the response from the getAccessToken()
function above to securely request your top tracks. This assumes you added the user-top-read
scope during the Authentication Phase.
const TOP_TRACKS_ENDPOINT = `https://api.spotify.com/v1/me/top/tracks`
export const getTopTracks = async () => {
const { access_token } = await getAccessToken()
return fetch(TOP_TRACKS_ENDPOINT, {
headers: {
Authorization: `Bearer ${access_token}`,
},
})
}
Here's what is happening from the code above:
- Destructure the access_token from the
getAccessToken()
function - Perform a
GET
request to theTOP_TRACKS_ENDPOINT
using the access_token - Return the Top Tracks data
Requesting Currently Playing Song Data from Spotify
const NOW_PLAYING_ENDPOINT = `https://api.spotify.com/v1/me/player/currently-playing`
export const getNowPlaying = async () => {
const { access_token } = await getAccessToken()
return fetch(NOW_PLAYING_ENDPOINT, {
headers: {
Authorization: `Bearer ${access_token}`,
},
})
}
The above code is the same as the process of fetching top tracks data. The difference is the endpoint it is fetching from. In this case, the NOW_PLAYING_ENDPOINT. This also assumes you added the user-read-currently-playing scope during the Authentication Phase.
Overall, here's the full code from your lib/spotify.js
file:
import querystring from 'querystring'
const client_id = process.env.SPOTIFY_CLIENT_ID
const client_secret = process.env.SPOTIFY_CLIENT_SECRET
const refresh_token = process.env.SPOTIFY_REFRESH_TOKEN
const basic = Buffer.from(`${client_id}:${client_secret}`).toString('base64')
const NOW_PLAYING_ENDPOINT = `https://api.spotify.com/v1/me/player/currently-playing`
const TOP_TRACKS_ENDPOINT = `https://api.spotify.com/v1/me/top/tracks`
const TOKEN_ENDPOINT = `https://accounts.spotify.com/api/token`
const getAccessToken = async () => {
const response = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: {
Authorization: `Basic ${basic}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: querystring.stringify({
grant_type: 'refresh_token',
refresh_token,
}),
})
return response.json()
}
export const getNowPlaying = async () => {
const { access_token } = await getAccessToken()
return fetch(NOW_PLAYING_ENDPOINT, {
headers: {
Authorization: `Bearer ${access_token}`,
},
})
}
export const getTopTracks = async () => {
const { access_token } = await getAccessToken()
return fetch(TOP_TRACKS_ENDPOINT, {
headers: {
Authorization: `Bearer ${access_token}`,
},
})
}
Creating an API route
Now, let us test our communication with Spotify.
Top Tracks API Route
First, we're going to create a new file at pages/api/top-tracks.js
. Then, import the getTopTracks
function from lib/spotify.js
.
import { getTopTracks } from '../../lib/spotify'
export default async (_, res) => {
const response = await getTopTracks()
const { items } = await response.json()
const tracks = items.slice(0, 10).map((track) => ({
artist: track.artists.map((_artist) => _artist.name).join(', '),
songUrl: track.external_urls.spotify,
title: track.name,
}))
res.setHeader('Cache-Control', 'public, s-maxage=86400, stale-while-revalidate=43200')
return res.status(200).json({ tracks })
}
This will return the first ten top tracks, formatted to remove unnecessary information, and then cache it. Feel free to modify this as you see fit.
If you'll notice, we cached the data that we got from getTopTracks
. We set the s-maxage to expire for 86400 seconds or 24 hours and revalidate the data after 43200 seconds or 12 hours. The stale-while-revalidate
indicates the client will accept a stale response, while asynchronously checking in the background for a fresh one.
So, if everything worked correctly, you should see some data like this https://luigicruz.dev/api/top-tracks after running or deploying your app.
Now Playing API Route
Second, we're going to create a new file at pages/api/now-playing.js
. Then, import the getNowPlaying
function from lib/spotify.js
.
import { getNowPlaying } from '../../lib/spotify'
export default async (_, res) => {
const response = await getNowPlaying()
if (response.status === 204 || response.status > 400) {
return res.status(200).json({ isPlaying: false })
}
const song = await response.json()
const isPlaying = song.is_playing
const title = song.item.name
const artist = song.item.artists.map((_artist) => _artist.name).join(', ')
const album = song.item.album.name
const albumImageUrl = song.item.album.images[0].url
const songUrl = song.item.external_urls.spotify
res.setHeader('Cache-Control', 'public, s-maxage=60, stale-while-revalidate=30')
return res.status(200).json({
album,
albumImageUrl,
artist,
isPlaying,
songUrl,
title,
})
}
In the above code, we first check if the response is 204 "No content" or 400 and above "Client Errors". If yes, return a JSON response of isPlaying: false. If not, we then store the data that we need from the response object into their own variables. You can use destructuring to store data into a variable if you want. Next is we cache the data for a s-maxage of 60 seconds and revalidate it for every 30 seconds.
So, if everything worked correctly, you should see some data like this https://luigicruz.dev/api/now-playing after running or deploying your app.
Adding the Spotify Widget
You can easily add your playlists using the Spotify Widget. I think it's cool to have a mini player that is why I added it on my website. To do this, you'll just have to embed it using an iframe and then you're good to go. Follow this guide on how you can do that.
This tutorial looks like a lot but the process is actually very simple. We register for an App that will allow us to communicate to the Spotify server, obtain authorization to access data by authentication or logging in, we received a refresh_token which we then used to request access token, we then request for our top-tracks and currently-playing data using the access token, and setup a API endpoint for both which then we can use to display in our pages.
It was a fun and unique project for me so I hope you have learned a lot from it. Feel free to message me on Twitter or Facebook if you have questions.
That's it for me. Until next time!