may 19, 2024

Spotify Developer API

On the projects page, you can see what I'm currently listening to and what my top tracks are on Spotify. It was an interesting project to implement, helped by this blog https://navidanindya.info/writing/spotify-now-playing-nuxt/ . Setting up the API, retrieving data via Postman requests, and then calling the Spotify API using fetch requests gave me good insight to OAuth.

Create an app on the Spotify developer dashboard

I first needed to visit the Spotify Developer Dashboard and log in with my Spotify credentials to create a new app.

This generated a Client ID and Client Secret which I saved somewhere safe. I set the redirect URI to the local version of my site, which was http:localhost:3000.

💡 When I later deployed the site later, I had to update the redirect URI to match the deployed site's URL.

Get an auth code

To authorize my account, I needed to create a URL that logs into Spotify and grants permission to the required scopes. Here's what I needed to construct the URL:

  • The client_id.
  • The redirect_uri.
  • The appropriate scopes for the information I wanted. I needed the current playing track and my top tracks, so I included all three scopes below.
https://accounts.spotify.com/authorize?client_id=<MY_CLIENT_ID>&response_type=code&redirect_uri=<MY_REDIRECT_URI>&scope=user-read-currently-playing,user-read-playback-state,user-read-recently-played,user-top-read

After authorization, I was redirected back to my site with a code query parameter in the url. This code is needed to obtain a refresh token.

http://localhost:3000/callback?code=AQC7P..gBoDU..

Get a refresh token

The client_id:client_secret combination needs to be encoded in Base64 format. I could have used a site like https://www.base64encode.org/. or print the result to the console:

const encoded = Buffer.from(`${client_id}:${client_secret}`).toString("base64");

I used Postman for sending the requests. I set the endpoint, the headers and the body as x-www-form-urlencoded fields.

curl --location 'https://accounts.spotify.com/api/token' \
--header 'Authorization: Basic <MY_BASE64_STRING>' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=<MY_REDIRECT_CODE>' \
--data-urlencode 'redirect_uri=<MY_REDIRECT_URI>'

The response from the endpoint has some fields but the important field I wanted was the refresh_token. This token doesn't expire, so I needed to keep it really safe.

Get an access token

Obtaining the access token follows the same process as getting the refresh token. Isimply replaced the code with the refresh_token.

curl --location 'https://accounts.spotify.com/api/token' \
--header 'Authorization: Basic <MY_BASE64_STRING>'Mzc5YjU=' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'refresh_token=<MY_REFRESH_TOKEN>' \
--data-urlencode 'grant_type=refresh_token'

The important field this time is the access_token which is used to retrieve my data and expires in one hour from the request time. Below is the JSON response returned from Postman.

{
  "access_token": "BQBYmAESfVG4lfO0xe.......",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "user-read-playback-state user-read-currently-playing user-read-recently-played user-top-read"
}

Get Spotify now playing

To get my currently playing track, I sent a GET request using the access_token I received.

curl --location 'https://api.spotify.com/v1/me/player/currently-playing' \
--header 'Authorization: Bearer <MY_ACCESS_TOKEN>'

The response from Spotify is a large JSON object with extensive information about the song. The key details I was interested in were the track title (item name) and the artist (an array).

{
  "item": {
    "artists": [
      {
        "id": "48vDIufGC8ujPuBiTxY8dm",
        "name": "Palace",
        "type": "artist"
      }
    ],
    "explicit": false,
    "name": "Make You Proud"
  },
  "is_playing": true
}

Get Spotify top tracks

To get the top tracks I've been listening to, I sent a GET request using the same access_token I received. There are two optional query parameters I've included. The long term time_range looks at listening history over the past year and the limit is set to return 5 items only.

GET: https://api.spotify.com/v1/me/top/tracks?time_range=long_term&limit=5

Headers: {
  Authorization: Bearer <Access_Token>
}

curl --location 'https://api.spotify.com/v1/me/top/tracks?time_range=long_term&limit=5' \
--header 'Authorization: Bearer <MY_ACCESS_TOKEN>'

And when all my scopes are set up correctly and my tokens are correct, Spotify returns with this response:

{
  "items": [
    {
      "album": {
        "artists": [
          {
            "name": "ANOTR"
          },
          {
            "name": "Abel Balder"
          }
        ],
        "name": "Relax My Eyes",
        "release_date": "2022-11-04"
      },
      "isExternal_urls": {
        "spotify": "https://open.spotify.com/track/5u4hhtZ7f4rWkMZEZcTKrH"
      },
      "name": "Relax My Eyes",
      "popularity": 78
    }
  ]
}

Setting it all up

Once I had everything working in Postman, I could finally move my work over to my React site and show some data. Here is how the fetch request looked:

// api/fetch-current-track.js

export const fetchCurrentTrack = async () => {
  const response = await fetch(
    "https://api.spotify.com/v1/me/player/currently-playing",
    {
      headers: {
        Authorization: `Bearer <MY_ACCESS_TOKEN>`,
      },
    }
  );

  if (!response.ok) {
    throw new Error("Failed to fetch current track");
  }

  if (response.status === 204) {
    return { isPlaying: false };
  }

  const data = await response.json();
  return {
    isPlaying: data.is_playing,
    trackTitle: data.item.name,
    artist: data.item.artists.map((artist) => artist.name).join(", "),
    albumArtUrl: data.item.album.images[0].url,
    trackUrl: data.item.external_urls.spotify,
    preview: data.item.preview_url,
  };
};

To use this request, I needed to call fetchCurrentTrack() and display the data. I used TanStack Query since its super performant and easy to learn. Here's the final component that I added.

export const CurrentPlay = () => {
  const currentTrack = useQuery({
    queryKey: ["currentTrack"],
    queryFn: fetchCurrentTrack,
    refetchInterval: 30000, // Refetch every 30 seconds
  });

  if (currentTrack?.isLoading) return <div>Loading...</div>;

  if (currentTrack.data) {
    return (
      <main>
        <h1>Now Playing:</h1>
        <div>
          <a href={trackData.trackUrl}>
            <h3>{data.trackTitle}</h3>
            <p>{data.artist}</p>
          </a>
        </div>
      </main>
    );
  }
};

Recently Played:

Album Art

Slutty Siri

LCY

Going forward

I'm continuously improving this Spotify implementation and have added the following:

  • a music preview that you can play and pause
  • dynamic colours based on the album art
  • showing most recently played if I'm not listening to anything right now

The options are endless.