Retrieving a persons Spotify playlist in Next.js

Chris Bongers - Oct 26 '21 - - Dev Community

In this article, we will be authentication a user with the Spotify API and retrieving their playlists!

This is a super good example of using NextAuth.js in Next.js and talking to the Spotify API.

Don't worry if you don't have any experience yet. I'll guide you through the whole process step by step.

Setting up a new Next.js project

We'll start by creating a new Next.js project. All we need to do for this is run the following command.

npx create-next-app
Enter fullscreen mode Exit fullscreen mode

Give your app a cool name, and you're ready to roll.

Then we need to install some dependencies for NextAuth to work.

npm install next-auth@beta
Enter fullscreen mode Exit fullscreen mode

And that's it, all the dependencies we need.
You can run the app by navigating to the folder and running the following command.

npm run dev
Enter fullscreen mode Exit fullscreen mode

Creating a Spotify app

Head over to the Spotify dashboard for developers and log in with your Spotify account.

You need a Spotify account for this

You can then go ahead a create a new app.

Spotify new app

Give this app a name and description and accept the terms.

Once you are done, you need to retrieve the Client ID and Client Secret from the app detail page.

Spotify application credentials

While we are here, we need to edit the applications settings.
Click the "Edit settings" button and fill out the redirect URL to include this one:

http://localhost:3000/api/auth/callback/spotify
Enter fullscreen mode Exit fullscreen mode

Now head back to your app and create a .env file, inside create the following two variables and set them to the credentials you just retrieved.

SPOTIFY_CLIENT_ID={ID}
SPOTIFY_CLIENT_SECRET={SECRET}
Enter fullscreen mode Exit fullscreen mode

Setting up the authentication

Now we'll need to introduce the authentication layer so the user can authenticate with Spotify.

We already added AuthNext as a dependency, so that's settled.
Let's create the API layer for NextAuth.js.

We need to create an auth folder inside our pages/api folder to get this to work.
Inside this folder create a file called [...nextauth].js.

import NextAuth from 'next-auth';
import SpotifyProvider from 'next-auth/providers/spotify';

export default NextAuth({
  providers: [
    SpotifyProvider({
      authorization:
        'https://accounts.spotify.com/authorize?scope=user-read-email,playlist-read-private',
      clientId: process.env.SPOTIFY_CLIENT_ID,
      clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
    }),
  ],
  callbacks: {
    async jwt({token, account}) {
      if (account) {
        token.accessToken = account.refresh_token;
      }
      return token;
    },
    async session(session, user) {
      session.user = user;
      return session;
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Here we load the Spotify provider for NextAuth.js.
The thing we do differently here is to add our own callback.

We do this custom callback because we want to use the access token for the user.

This will set the refresh token for the user as the access token we can then use by attaching it to the session.

The next thing we need to do is wrap our app with the NextAuth.js context provider.
Open your pages/_app.js/ file and change it, so it looks like this.

import {SessionProvider} from 'next-auth/react';
import '../styles/globals.css';

export default function App({Component, pageProps: {session, ...pageProps}}) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

This will wrap our app with a session provider so that we can access the session quickly.

Now let's add a login button to our app.
We do this in the pages/index.js file.

import {useSession, signIn, signOut} from 'next-auth/react';

export default function Home() {
  const {data: session} = useSession();

  if (session) {
    return (
      <>
        Signed in as {session?.token?.email} <br />
        <button onClick={() => signOut()}>Sign out</button>
      </>
    );
  }
  return (
    <>
      Not signed in <br />
      <button onClick={() => signIn()}>Sign in</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

If the user is signed in, we show his email address from the token. Else we offer the signup button.

The flow will look like this:

Retrieving a person Spotify playlist in Next.js

Retrieving the playlists for this user

Now let's see how we can query the Spotify API to retrieve the playlists for this user.

Create a lib folder in your project, and inside, create a spotify.js file.

The first thing we need to do here is adding the variables we need.

const client_id = process.env.SPOTIFY_CLIENT_ID;
const client_secret = process.env.SPOTIFY_CLIENT_SECRET;
const basic = Buffer.from(`${client_id}:${client_secret}`).toString('base64');
const TOKEN_ENDPOINT = `https://accounts.spotify.com/api/token`;
const PLAYLISTS_ENDPOINT = 'https://api.spotify.com/v1/me/playlists';
Enter fullscreen mode Exit fullscreen mode

This will load our client id and secret and create a basic authentication from that.
Then we also add the token endpoint and the playlist endpoint.

Then we need a function that will turn our refresh token into an access token for a specific request.

const getAccessToken = async (refresh_token) => {
  const response = await fetch(TOKEN_ENDPOINT, {
    method: 'POST',
    headers: {
      Authorization: `Basic ${basic}`,
      '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

This function will call the token endpoint and pass our refresh token in.
In return, we get an access token we can use to query the API. However, this token is not valid forever.

Then we, of course, need a function that will call the playlist endpoint for a user.

export const getUsersPlaylists = async (refresh_token) => {
  const {access_token} = await getAccessToken(refresh_token);
  return fetch(PLAYLISTS_ENDPOINT, {
    headers: {
      Authorization: `Bearer ${access_token}`,
    },
  });
};
Enter fullscreen mode Exit fullscreen mode

Here we first retrieve an access token for a specific request token.
Then we return a fetch request to the playlists endpoint.

However, this is now a private function, so we don't have a way to call it yet.

I'll create an API endpoint for this as this is the safest way to call this function.

Inside your pages/api folder, create a new file called playlists.js.

import {getUsersPlaylists} from '../../lib/spotify';
import {getSession} from 'next-auth/react';

const handler = async (req, res) => {
  const {
    token: {accessToken},
  } = await getSession({req});
  const response = await getUsersPlaylists(accessToken);
  const {items} = await response.json();

  return res.status(200).json({items});
};

export default handler;
Enter fullscreen mode Exit fullscreen mode

Now, if we call http://localhost:3000/api/playlists with our logged-in user, we should see some results!

Creating a frontend action to retrieve playlists

This is pretty cool, but let's add a simple frontend.
What we want is a button, that on click, will retrieve all playlists for this user.

Open your index.js file and make the following changes:

import {useSession, signIn, signOut} from 'next-auth/react';
import {useState} from 'react';

export default function Home() {
  const {data: session} = useSession();
  const [list, setList] = useState([]);

  const getMyPlaylists = async () => {
    const res = await fetch('/api/playlists');
    const {items} = await res.json();
    setList(items);
  };

  if (session) {
    return (
      <>
        Signed in as {session?.token?.email} <br />
        <button onClick={() => signOut()}>Sign out</button>
        <hr />
        <button onClick={() => getMyPlaylists()}>Get all my playlists</button>
        {list.map((item) => (
          <div key={item.id}>
            <h1>{item.name}</h1>
            <img src={item.images[0]?.url} width="100" />
          </div>
        ))}
      </>
    );
  }
  return (
    <>
      Not signed in <br />
      <button onClick={() => signIn()}>Sign in</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here we made use of the useState hook to create a new variable.
Then we introduce a simple function that will call our API endpoint and set this variable.

And then we add a button which on click calls this function and loop over all the results!

It is resulting in our playlists being fetched!

List of all Spotify playlists for a user

Now it's up to you to add some cool styling to this 😎.
You can find the complete code on GitHub.

Thank you for reading, and let's connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .