Voltar ao blog
Integrando a API do Spotify no Meu Portfólio

Integrando a API do Spotify no Meu Portfólio

Compartilhar
Eduardo RigoEduardo Rigo24 de março de 20263 min de leitura
SpotifyAPINext.jsOAuth

Por Que Mostrar o Que Estou Ouvindo?

Eu queria que meu portfólio tivesse vida — não apenas uma lista estática de projetos. Mostrar o que estou ouvindo no Spotify adiciona um toque pessoal e dá aos visitantes um vislumbre de quem eu sou além do código.

O problema? A API do Spotify não é exatamente plug-and-play. Tem uma dança de OAuth, refresh tokens e edge cases pra lidar quando nada está tocando.

O Fluxo OAuth (A Parte Dolorosa)

O Spotify usa OAuth 2.0 com Authorization Code Flow. O resumo:

  1. Você autoriza o app no Spotify Developer Dashboard
  2. Recebe um authorization code (uso único)
  3. Troca por um access token + refresh token
  4. O access token expira a cada hora — o refresh token é sua salvação

A parte chata é conseguir o refresh token inicial. Você precisa acessar manualmente a URL de autorização no navegador, pegar o code do redirect e trocar via cURL:

curl -X POST https://accounts.spotify.com/api/token \
  -H "Authorization: Basic <base64(client_id:client_secret)>" \
  -d grant_type=authorization_code \
  -d code=<seu_auth_code> \
  -d redirect_uri=http://localhost:3000/callback

Uma vez que você tem o refresh token, salva como variável de ambiente. Nunca mais vai precisar fazer isso (a menos que revogue o acesso).

O Route Handler

Criei uma rota API com Edge Runtime que lida com o fluxo completo a cada request:

// 1. Troca o refresh token por um access token novo
const getAccessToken = async () => {
  const body = new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: refresh_token
  })

  const response = await fetch(TOKEN_ENDPOINT, {
    method: 'POST',
    headers: {
      Authorization: `Basic ${btoa(`${client_id}:${client_secret}`)}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: body.toString(),
    cache: 'no-store',
  })

  return response.json()
}

O btoa() codifica as credenciais como Base64 — o Spotify exige isso no endpoint de token.

Now Playing vs. Last Played

A API tem dois endpoints relevantes:

  • /v1/me/player/currently-playing — retorna a música tocando agora (ou 204 No Content se nada estiver tocando)
  • /v1/me/player/recently-played — retorna as últimas 50 músicas tocadas

Minha lógica é simples: tenta "tocando agora" primeiro, faz fallback pra "tocou recentemente":

const nowPlaying = await getNowPlaying(access_token)

if (nowPlaying?.is_playing && nowPlaying.currently_playing_type === 'track') {
  return { isPlaying: true, title: nowPlaying.item.name, ... }
}

// Nada tocando — pega a música mais recente
const lastPlayed = await getLastPlayed(access_token)
const mostRecent = lastPlayed.items.sort(
  (a, b) => new Date(b.played_at).getTime() - new Date(a.played_at).getTime()
)[0]

return { isPlaying: false, title: mostRecent.track.name, ... }

O Client Side: SWR pra Updates em Tempo Real

No frontend, uso SWR pra fazer polling na API a cada 10 segundos:

const { data, isValidating, mutate } = useSWR<SpotifyData>(
  '/api/spotify',
  fetcher,
  {
    refreshInterval: 10000,
    revalidateOnFocus: false,
    dedupingInterval: 5000,
  }
)

Isso me dá:

  • Auto-refresh a cada 10 segundos
  • Deduplicação pra não sobrecarregar a API
  • Uma função mutate() pro botão de refresh manual
  • A capa do álbum gira com uma animação do Framer Motion quando uma música está tocando

Caching: Não Cachear Isso

Como o objetivo é dados em tempo real, cache é o inimigo:

export const runtime = 'edge'
export const dynamic = 'force-dynamic'

// Mais headers agressivos de no-cache em cada response
const headers = {
  'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
  'Pragma': 'no-cache',
  'Expires': '0',
}

Edge Runtime mantém o cold start rápido, e force-dynamic garante que o Next.js não tente otimizar a rota estaticamente.

Problemas Que Encontrei Pelo Caminho

  • O refresh token pode expirar se você não usar por 6 meses ou se revogar o acesso. Guarde-o em um lugar seguro.
  • Podcasts quebram tudo — o endpoint currently-playing retorna podcasts também. Sempre cheque currently_playing_type === 'track'.
  • Rate limits — O Spotify permite ~180 requests por minuto. Com polling de 10s e poucos visitantes, tá de boa. Mas não baixe mais que isso.
  • Variáveis de ambiente — Uso tanto SPOTIFY_* (servidor) quanto NEXT_PUBLIC_SPOTIFY_* (legado) com um padrão de fallback.

O Resultado Final

Um card pequeno que mostra o que estou ouvindo em tempo real, com a capa do álbum girando e um timestamp relativo pra última música tocada:

Card tocando agora

E aqui a resposta crua da API que alimenta tudo — batendo em /api/spotify retorna tudo que o frontend precisa:

Resposta da API do Spotify

É uma feature pequena, mas faz o site parecer mais que um currículo.