Displaying Your Top Songs Using the Spotify API

Displaying Your Top Songs Using the Spotify API

Luigi Cruz / June 20, 2021

10 min read

One of my favorite parts of my website is the Playlists page. Back in the days when Friendster was a thing, you are allowed to customize your profile page. 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 my top tracks. At first, I was looking for a way to have a full-feature Spotify app on my website but sadly, it is still on beta. It was still cool though that I can integrate some Spotify features with my website.

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.

  1. Go to your Spotify Developer Dashboard and log in.
  2. Click Create an App.
  3. Enter Application Name and Application Description and then click CREATE.
  4. Click Show Client Secret.
  5. Save your Client ID and Client Secret.
  6. Click Edit Settings.
  7. Add your website URL. // Ex.: https://luigicruz.dev
  8. 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.

Spotify Authorization Code Flow

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 to code
  • 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:

  1. The user is asked to authorize access within the scopes. // You will be prompted to log in to your Spotify account.
  2. 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.

lib/spotify.js
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.

lib/spotify.js
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 the TOP_TRACKS_ENDPOINT using the access_token
  • Return the Top Tracks data

Requesting Currently Playing Song Data from Spotify

lib/spotify.js
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:

lib/spotify.js
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.

pages/api/top-tracks.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.

pages/api/now-playing.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!

cd ..

Did you find this post useful? I would appreciate if you could share this to your networks.