Spotify API Integration with NextJS: Build Your Own Now Playing

When it was time to give my portfolio a facelift (because, you know, dev life), I figured why not go big and try Next.js v14 along with the app folder structure. Rolls up sleeves, cracks knuckles—time to code! Now, let me save you some trial and error—here’s how you can do it too.

Why are we using nextJS’s API route to fetch our data?

  • Server-Side Security: Sensitive credentials like client_id, client_secret, and refresh_token should not be exposed to the client (browser). By using Next.js API routes, we can securely store and handle these values server-side, preventing them from being exposed to the public.
  • Token Management: By using a server-side API route, we can handle token refreshing logic securely and transparently without involving the client.
  • Caching and Optimizations: With server-side requests, you can implement caching, rate limiting, and other performance optimizations like s-maxage and stale-while-revalidate, which can improve performance.
  • Centralized Data Fetching: Fetching data from Spotify on the server ensures that all client requests go through a central point, allowing for better control over logging.

Service Architecture

Our Service Architecture

Step One: Sign up as a Spotify Developer

To access your Spotify data and communicate with Spotify’s servers, you’ll need some tokens. Here’s how you can generate them:

  1. Head over to Spotify for Developers and sign in with your Spotify account to register as a developer.

  2. Click the Create App button and fill in the required details—here’s an example to guide you.

    Soptify Create App
  3. Now, go to the project you just created and make sure to note down the Client ID and Client Secret. You'll need these for authentication.

    Soptify Basic Info
  4. You should now have the following::

SPOTIFY_CLIENT_ID=<Client ID>
SPOTIFY_CLIENT_SECRET=<Client secret>

StepTwo: Generating a Refresh Token for the Spotify API

We'll need to generate a refresh token for our API so that each time a user visits the site, a new access token is created to access our Spotify data. Spotify uses the OAuth 2.0 authorization framework for this process. You can dive deeper into the details here: OAuth 2.0 Documentation.

  1. To generate the authorization code with permissions to access your now-playing and top-songs data, open the following URL in your browser after replacing <SPOTIFY_CLIENT_ID> with your actual client ID:

    https://accounts.spotify.com/authorize?client_id=<SPOTIFY_CLIENT_ID>&response_type=code&redirect_uri=http%3A%2F%2Flocalhost:3000&scope=user-read-currently-playing%20user-top-read
    
    Note: Keep the same redirect_uri as the one in your spotify dashboard.
    
  2. You’ll be redirected to the redirect_uri you configured, and the query parameter code will be attached to the URL. This code is what we’ll need to generate your SPOTIFY_REFRESH_TOKEN.

    Example:
    http://localhost:3000/?code=AQCU5snB4rmSCKyvhWOQOEcvqsYFy17n_PcDOdoMwDVPJs
    
  3. Head over to Base64 Encode to generate your encoded base64 string. You'll need to encode your <SPOTIFY_CLIENT_ID>:<SPOTIFY_CLIENT_SECRET> in this format.

    Encode your string in the format:
    SPOTIFY_CLIENT_ID:SPOTIFY_CLIENT_SECRET
    
    Note: Don't forget the ":" in between the values!
    
  4. Final Step: Open Postman or Insomnia and paste the following cURL command. Make sure that the redirect_uri and code match the ones from the previous steps

    curl --request POST \
      --url https://accounts.spotify.com/api/token \
      --header 'Authorization: Basic <YOUR_BASE64_STRING>' \
      --header 'Content-Type: application/x-www-form-urlencoded' \
      --data redirect_uri=http%3A%2F%2Flocalhost:3000 \
      --data grant_type=authorization_code \
      --data code=<YOUR_AUTHORIZATION_CODE>
    
  5. And, now you’ll have the SPOTIFY_REFRESH_TOKEN with the response JSON:

    {
      "access_token": "BQCE8nQzic.....rHNr",
      "token_type": "Bearer",
      "expires_in": 3600,
      "refresh_token": "AQC....c5UgIxSpl8EA",
      "scope": "user-read-currently-playing user-top-read"
    }
    

StepThree: Writing our fetcher functions

Now let's write our fetcher functions that will handle the Spotify API requests, using the SPOTIFY_REFRESH_TOKEN to generate an access token and fetch the necessary data.

  1. Store the Spotify Tokens in the .env file of your nextJS project:

    SPOTIFY_CLIENT_ID=<Client ID>
    SPOTIFY_CLIENT_SECRET=<Client secret>
    SPOTIFY_REFRESH_TOKEN=<refresh_token>
    
  2. Create a spotify.ts file inside of your nextJS project, I prefer the path to be /src/lib and we’ll define our Spotify data-fetching functions here:

    1. getAccessToken ⇒ Retrieves a new access token using the refresh token.
    2. fetchFromSpotify ⇒ Utilizes the access token to fetch data from Spotify.
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 ENDPOINTS = {
  nowPlaying: 'https://api.spotify.com/v1/me/player/currently-playing',
  topTracks: 'https://api.spotify.com/v1/me/top/tracks',
  topArtists: 'https://api.spotify.com/v1/me/top/artists',
}

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,
    }),
  })

  const data = await response.json()
  return data.access_token
}

const fetchFromSpotify = async (endpoint: string) => {
  const access_token = await getAccessToken()
  return fetch(endpoint, {
    headers: {
      Authorization: `Bearer ${access_token}`,
      'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=30',
    },
  })
}

export const getNowPlaying = async () => fetchFromSpotify(ENDPOINTS.nowPlaying)
export const getTopTracks = async () => fetchFromSpotify(ENDPOINTS.topTracks)
export const getTopArtists = async () => fetchFromSpotify(ENDPOINTS.topArtists)

StepFour: Serving the Data Using Next.js API Routes

Now we’ll use Next.js API routes to serve the Spotify data. This allows you to securely fetch the data on the server-side and return it to your frontend.

Now Playing - /api/playing

Create a file at /src/app/api/playing/route.ts, which will generate an endpoint at /api/playing. We'll set the revalidation interval to 120 seconds, which is more than enough to keep the currently playing song updated.

import { getNowPlaying } from '@/lib/spotify'

type Artist = { name: string }

export const revalidate = 120

export async function GET() {
  const response = await getNowPlaying()

  if (!response.ok || response.status === 204) {
    return Response.json({ isPlaying: false })
  }

  const song = await response.json()
  const item = song.item

  if (!item) {
    return Response.json({ isPlaying: false })
  }

  const { is_playing: isPlaying } = song
  const { name: title, album, artists, external_urls } = item
  const artist = artists.map((artist: Artist) => artist.name).join(', ')
  const albumImageUrl = album.images[0]?.url
  const songUrl = external_urls.spotify

  return Response.json({
    album: album.name,
    albumImageUrl,
    artist,
    isPlaying,
    songUrl,
    title,
  })
}

Spotify Documentation for the API: https://developer.spotify.com/documentation/web-api/reference/get-the-users-currently-playing-track

Top Tracks - /api/tracks

Create a file at /src/app/api/tracks/route.ts, which will provide an endpoint at /api/tracks. We'll configure the revalidation interval to 86,400 seconds (1 day), since this data doesn't need frequent updates.

import { getTopTracks } from '@/lib/spotify'

type Artist = { name: string }

type Track = {
  artists: Artist[]
  external_urls: { spotify: string }
  name: string
}

export const revalidate = 86400

export async function GET() {
  const response = await getTopTracks()
  const { items } = await response.json()

  const tracks = items
    .slice(0, 10)
    .map(({ artists, external_urls, name }: Track) => ({
      artist: artists.map((artist) => artist.name).join(', '),
      songUrl: external_urls.spotify,
      title: name,
    }))

  return Response.json({ tracks })
}

Spotify Documentation for the API: https://developer.spotify.com/documentation/web-api/reference/get-users-top-artists-and-tracks

Bonus: Top Artists

You can create a similar API to fetch the top artists by using Spotify's "Get User's Top Artists" endpoint. Here’s how you can set it up:

  1. Create a file at /src/app/api/artists/route.ts, which will give you an endpoint at /api/artists.
  2. Set the revalidation config to 86400 seconds (1 day), as the top artist data doesn’t update frequently.

StepFive: Consuming the data on the Frontend

On any page where you want to use this API, you can call the endpoint as shown in the following example:

import { SimpleLayout } from '@/components/SimpleLayout'

export const revalidate = 60 * 60 * 24

export default async function Stats() {
  const response = await fetch(`${process.env.NEXT_PUBLIC_SITE_URL}/api/tracks`)
  const data: TopTracks = await response.json()

  return (
    <SimpleLayout
      title="Music"
      intro="My current jam? Got it listed right here!"
    >
      // Your components here
    </SimpleLayout>
  )
}

Also, don’t miss the footer for a live update of what I’m currently listening to!