DEV Community

Cover image for How to use Spotify API with Next.js
Jatin Sharma
Jatin Sharma

Posted on • Updated on • Originally published at j471n.in

How to use Spotify API with Next.js

On my website, there is a stats page, which showcases many things such as my Dev.to blog stats and the GitHub repository count and the Spotify stats, we will talk about how you can implement that in your dream project.

Table of Contents

Preview

My Top 5 Tracks on Spotify

My Top 5 Artists on Spotify

When I am not playing any song

Currently Playing Song

Create a Spotify App

Firstly, we need to create an application on the Spotify-

  • Go to Spotify Developer Dashboard
  • Login with Spotify Account Login
  • Click on Create an App
  • Fill the Name and the Description then Click Create create
  • Save your Client ID and Secret in your .env.local
  • Click on Edit Setting on top-right and add the http://localhost:3000/callback as the callback url and click on Save. edit Setting

Everything is done. Now let's get into the authentication stuff.

Authentication with refresh_token

There are many types of authentication we are going with refresh_token because we only want to authenticate once. You can learn about Authorization here

Authorization Code Flow

Authorization Code

As you can see in the above image we have everything except scopes. Which defines, what we need fetch from the Spotify API.

To do that we need a url that send the following parameter to the Spotify-

  • response_type : it should be code (as shown in the image)
  • redirect_uri : callback url same as you enter in the Spotify Dashboard
  • client_id : your project's client_id
  • scope : you can read about that here

We want to fetch the user's top stats and the currently playing song. for that scope is user-top-read and user-read-currently-playing. You can add as many scopes as you want separated by plus ("+"). So, now our URL looks like this (enter your project client_id)-

# URL

https://accounts.spotify.com/authorize?client_id=bda10cd719564898818245d97727de7e&response_type=code&redirect_uri=http://localhost:3000/callback&scope=user-read-currently-playing+user-top-read 
Enter fullscreen mode Exit fullscreen mode

Now enter the URL in the browser and then click Enter. After authorizing, you'll be redirected back to your redirect_uri. In the URL, there's a code query parameter. Store this value somewhere because we are going to need it in the next step.

# code
http://localhost:3000/callback?code=CDxRCu......NLdsM
Enter fullscreen mode Exit fullscreen mode

Get refresh_token

After doing all this work now we need to get the refresh_token to authorization. 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 exactly the same as this client_id:client_secret.

Your url should look like this -

# demo url to get "refresh_token"

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/callback https://accounts.spotify.com/api/token
Enter fullscreen mode Exit fullscreen mode

So after encoding the Client ID and Client Secret it should be looks like this-

YmRhMTBjZDcxOTU2NDg5ODgxODJmMmQ5NzcyN2RlN2U6NjZmZTU5OTJjYjZlNDFjZmEwNDdfkdjfkHKkjdfdwODk0YjI=
Enter fullscreen mode Exit fullscreen mode

Now the final url should looks like this-

curl -H "Authorization: Basic YmRhMTBjZDcxOTU2NDg5ODgxODJmMmQ5NzcyN2RlN2U6NjZmZTU5OTJjYjZlNDFjZmEwNDdfkdjfkHKkjdfdwODk0YjI=" -d grant_type=authorization_code -d code=AQD_kIzZ0OVXkeeIU4jnLWADFJDKdcWLYo9ySdfdfdkHKJFtGExxgw_aCxJrZflWVeARvjaGDfdfddf6KYpFlo2t2JZ0SQceyvdKs4AcGgCLSqm7vMyiRWCy_t06WmLet8v6aEBx8U4eKHxKiEx_sBgvCSlYo5wptKUd0Gwa6oyqOHFZnE7KSbxTVwMkQ668Ezjz2aeFEPp1TU9ij6dM4AjO4YFCH2hoMWgO3k5XrbdKnK7U2Y30wb8gHjEA6LnE8yu8cyvaY9WQ -d redirect_uri=http%3A%2F%2Flocalhost:3000/callback https://accounts.spotify.com/api/token
Enter fullscreen mode Exit fullscreen mode

After running this on your terminal it will return a JSON object containing refresh_token. This token is valid indefinitely unless you revoke access, so we'll want to save this in an environment variable. The JSON will looks like this-

{
  "access_token":"DKjdfkdjkdfjdRU67VlhTLc3HUHHRvcG6W3F56ISDFodvSiSmFZIIUwOzzZCyCcgOia-TXX-y_wq8n7jEHTI0JEYO6djABMQmlutMqsUPgEiVJ2isj98BrBYFV4dfdsjkfksudJKDUIkdfdfdk1vxR07V_r7Y-aLjMyqwBaSqxneFokM",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token":"dfkdjkkdjfdfrrdkfjd9984maWbrIrXqNNK63SOzJdWQGaTYQGD6Ji1dWsAywEgm-eUspfuOlug8f71XL2oBWtD_pzd6EzCWwqCVnDSWspbJelOwVDY62TuAI",
  "scope": "user-read-currently-playing user-top-read user-read-private"
}

Enter fullscreen mode Exit fullscreen mode

That's a lot to digest, but now our work is done, we have all the necessary things we needed for authorization.

Setting up Environment Variables

Now all the things we get in the above process we need to add those to the .env.local

SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=
SPOTIFY_REFRESH_TOKEN=
Enter fullscreen mode Exit fullscreen mode

Using Spotify API

Now we can use the refresh_token to get the final access_token. The following Code fetches the access_token by using the refresh_token

// lib/spotify.js

const getAccessToken = async () => {
  const refresh_token = process.env.SPOTIFY_REFRESH_TOKEN;

  const response = await fetch("https://accounts.spotify.com/api/token", {
    method: "POST",
    headers: {
      Authorization: `Basic ${Buffer.from(
        `${process.env.SPOTIFY_CLIENT_ID}:${process.env.SPOTIFY_CLIENT_SECRET}`
      ).toString("base64")}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({
      grant_type: "refresh_token",
      refresh_token,
    }),
  });

  return response.json();
};
Enter fullscreen mode Exit fullscreen mode

Fetch the Top Tracks

Now you can use this access_token fetch your data. In our case it is the top tracks.

// lib/spotify.js

export const topTracks = async () => {
  const { access_token } = await getAccessToken();

  return fetch("https://api.spotify.com/v1/me/top/tracks", {
    headers: {
      Authorization: `Bearer ${access_token}`,
    },
  });
};
Enter fullscreen mode Exit fullscreen mode

Creating API route top tracks

In the beginning, I mentioned that we are going to use the Next.js API routes. If you are not familiar with that then give this a read

// pages/api/stats/tracks.js

import { topTracks } from "../../../lib/spotify";

export default async function handler(req, res) {
  const response = await topTracks();
  const { items } = await response.json();

  const tracks = items.slice(0, 5).map((track) => ({
    title: track.name,
    artist: track.artists.map((_artist) => _artist.name).join(", "),
    url: track.external_urls.spotify,
    coverImage: track.album.images[1],
  }));

  res.setHeader(
    "Cache-Control",
    "public, s-maxage=86400, stale-while-revalidate=43200"
  );

  return res.status(200).json(tracks);
}
Enter fullscreen mode Exit fullscreen mode

This will return the first five top tracks, I've removed unnecessary information. You can modify this as you like. Now visit the localhost:3000/api/stats/tracks. If everything worked correctly, you should see some data like this -

// localhost:3000/api/stats/tracks

[
  // ...........
 {
    "title": "Blowing Up",
    "artist": "KR$NA",
    "url": "https://open.spotify.com/track/3Oh2FwWbnKIAyUE2gToFYu",
    "coverImage": {
      "height": 300,
      "url": "https://i.scdn.co/image/ab67616d00001e02f8c35169d5bab01327f87e5a",
      "width": 300
    }
  },
  {
    "title": "Jaan Bolti Hai",
    "artist": "Karma",
    "url": "https://open.spotify.com/track/4KGZlLtfKV4raRbsoB2Rw9",
    "coverImage": {
      "height": 300,
      "url": "https://i.scdn.co/image/ab67616d00001e028e3626063a42b11b847663b3",
      "width": 300
    }
  },
  //.........
]
Enter fullscreen mode Exit fullscreen mode

To see my stats you can visit https://jatin.vercel.app/api/stats/tracks.

Fetch the Top Artist

Now you can use this access_token fetch your data. In our case it is the top artists.

// lib/spotify.js

export const topArtists = async () => {
  const { access_token } = await getAccessToken();

  return fetch("https://api.spotify.com/v1/me/top/artists", {
    headers: {
      Authorization: `Bearer ${access_token}`,
    },
  });
};
Enter fullscreen mode Exit fullscreen mode

Creating API route top artists

// pages/api/stats/artists.js

import { topArtists } from "../../../lib/spotify";

export default async function handler(req, res) {
  const response = await topArtists();
  const { items } = await response.json();

  const artists = items.slice(0, 5).map((artist) => ({
    name: artist.name,
    url: artist.external_urls.spotify,
    coverImage: artist.images[1],
    followers: artist.followers.total,
  }));

  res.setHeader(
    "Cache-Control",
    "public, s-maxage=86400, stale-while-revalidate=43200"
  );

  return res.status(200).json(artists);
}
Enter fullscreen mode Exit fullscreen mode

It returns your's top Spotify artists.

Fetch the Currently Playing Song

Now, as you can see on my website's footer there is an option to see if I am currently playing the song or not, if yes then which one. To do that, we fetch the following URL.

// lib/spotify.js

export const currentlyPlayingSong = async () => {
  const { access_token } = await getAccessToken();

  return fetch("https://api.spotify.com/v1/me/player/currently-playing", {
    headers: {
      Authorization: `Bearer ${access_token}`,
    },
  });
};
Enter fullscreen mode Exit fullscreen mode

Creating API route for Currently Playing Song

// pages/api/now-playing.js

import { currentlyPlayingSong } from "../../lib/spotify";

export default async function handler(req, res) {
  const response = await currentlyPlayingSong();

  if (response.status === 204 || response.status > 400) {
    return res.status(200).json({ isPlaying: false });
  }

  const song = await response.json();

  if (song.item === null) {
    return res.status(200).json({ isPlaying: false });
  }

  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,
  });
}
Enter fullscreen mode Exit fullscreen mode

This returns all the song details if the user is playing the song. Otherwise, isPlaying will return false. You can handle both the conditions now. If the user is playing the song, then simply show the song Otherwise Display Not Playing.

You should use swr to fetch the data Otherwise it will show you the old data which you might not like.

Output

You can view the Example of this on my website

Wrapping Up

If you enjoyed this article then don't forget to press โค๏ธ. If you have any queries or suggestions don't hesitate to drop them. See you.

You can extend your support by buying me a Coffee.๐Ÿ˜Š๐Ÿ‘‡
buymecoffee

Top comments (15)

Collapse
 
robbiecren07 profile image
Robbie Crenshaw

When you went to production, did you have issues with the API getting a 500? It works fine on localhost. I even went through the steps for the refresh token again using the production domain and added the production website/callback in Spotify developer console. Did you have to make any code modifications for it to work in production? I'm deployed on Vercel using Nextjs.

Collapse
 
j471n profile image
Jatin Sharma • Edited

I am using the same code for production as well. I am just using swr package to fetch the data. And i didn't face any issues.

Feel free to go through the code. And if you wanna discuss this matter in details DM me on twitter

Collapse
 
robbiecren07 profile image
Robbie Crenshaw

I think I figured it out, I originally tweaked the code a little bit to use Nextjs Edge API Routes and I guess that is what was causing the issue.

Thread Thread
 
j471n profile image
Jatin Sharma

Thanks great ๐Ÿ‘

Collapse
 
xoboid profile image
xoboid

I found the app router stuff complicated and ended up using good old useState and useEffect

Collapse
 
jaredycw profile image
Jared Yeung • Edited

Hello, It's very clear.
Your example was done in Page router (/Page/... js /ts)
However, do you know how to do it in App router?
I don't know why in localhost, it displayed

This page isnโ€™t working If the problem continues, contact the site owner.
HTTP ERROR 405

Collapse
 
j471n profile image
Jatin Sharma

Hello,

I haven't use this in app router, however I am using API routes and then fetching the data on the client side.

But in next.js app router they have client components and server components, data fetching will be based on that.

You can follow the swr guide which should help you to get through this problem.

Collapse
 
jaredycw profile image
Jared Yeung

Thanks so much, I will try this in app router, it seems difficult in NextJS 13.4.

Thread Thread
 
j471n profile image
Jatin Sharma

They have changed a lot of things, so it's little complicated but not impossible. I think if you use client side rendering and use swr it should work.

Thread Thread
 
jaredycw profile image
Jared Yeung

Thanks Jatin, I just figured it out in App router.

Thread Thread
 
j471n profile image
Jatin Sharma

That's amazing, if you could share the code here... That will be helpful for the other readers โœŒ๐Ÿป

Thread Thread
 
jaredycw profile image
Jared Yeung • Edited

Beside App Router issue,
I found out that .env.local file (in my case) is not worked, the env code is needed to add into the next.config.js file (/root) once you have pasted all the key in .env.local.file (I dont know it's common in this issue, coz Im new in NextJS.)

const nextConfig = {

      reactStrictMode: true,
        env: {
            SPOTIFY_REFRESH_TOKEN: process.env.SPOTIFY_REFRESH_TOKEN,
        SPOTIFY_CLIENT_SECRET: process.env.SPOTIFY_CLIENT_SECRET,
        SPOTIFY_CLIENT_ID: process.env.SPOTIFY_CLIENT_ID,

          }

}

module.exports = nextConfig

Enter fullscreen mode Exit fullscreen mode

For lib/spotify.js file, It's the same, no need to change.

Only the API file is different, it's needed to create route.
Eg. the path and file is changed from "pages/api/stats/tracks.js (page router) to "app/api/tracks/router.js"(app router)
...
changed from "pages/api/stats/artists.js (page router) to "app/api/artists/router.js"(app router)

For the api/track.ts file script,


import { topTracks } from '@/app/lib/spotify';

 // In order to fix HTTP method / async,  changed from "export default async function handler(req, res)" to "export async function GET() "


export async function GET() {
  const response = await topTracks();
  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,
    coverImage: track.album.images[1],
    title: track.name
  }));

  return new Response(JSON.stringify({ tracks }), {
    status: 200,
    headers: {
      'content-type': 'application/json',
      'cache-control': 'public, s-maxage=86400, stale-while-revalidate=43200'
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

localhost:3000/api/stats/tracks

lastly, use swr to fetch the api file

**tested in nextjs 13, using typescript

Thread Thread
 
j471n profile image
Jatin Sharma

Thanks mate.

Collapse
 
vulcanwm profile image
Medea

thanks for this tutorial!
just found it right now and it really helps!

Collapse
 
j471n profile image
Jatin Sharma

I am glad it helped.