📡 Consumir APIs Musicales con Fetch

Autenticación, paginación y reproductor vanilla JS paso a paso

🎵 Buscador de Música

🔑 Autenticación con API Key

// Configuración de la API const API_CONFIG = { deezer: { baseUrl: 'https://api.deezer.com', key: 'tu-api-key-aqui', // En producción usa variables de entorno endpoints: { search: '/search', track: '/track/', artist: '/artist/' } }, lastfm: { baseUrl: 'https://ws.audioscrobbler.com/2.0/', key: process.env.LASTFM_API_KEY, format: 'json' } }; // Función de búsqueda con autenticación async function searchMusic(query, api = 'deezer', limit = 20, index = 0) { try { // Construir URL con parámetros const url = new URL(API_CONFIG[api].baseUrl + API_CONFIG[api].endpoints.search); url.searchParams.append('q', query); url.searchParams.append('limit', limit); url.searchParams.append('index', index); // Headers de autenticación const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_CONFIG[api].key}` }; // Realizar petición const response = await fetch(url, { method: 'GET', headers: headers }); // Verificar respuesta if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // Parsear JSON const data = await response.json(); return data; } catch (error) { console.error('Error en la búsqueda:', error); throw error; } } // Uso searchMusic('metallica', 'deezer', 10) .then(data => console.log('Resultados:', data)) .catch(error => console.error('Error:', error));

📄 Paginación de Resultados

class MusicAPIClient { constructor(apiConfig) { this.config = apiConfig; this.currentPage = 0; this.totalResults = 0; this.resultsPerPage = 20; } async search(query, page = 0) { this.currentPage = page; const index = page * this.resultsPerPage; try { const response = await this.makeRequest(query, index); this.totalResults = response.total || 0; return { data: response.data || response.results, pagination: { currentPage: page, totalPages: Math.ceil(this.totalResults / this.resultsPerPage), hasNext: index + this.resultsPerPage < this.totalResults, hasPrev: page > 0, totalResults: this.totalResults } }; } catch (error) { throw new Error(`Search failed: ${error.message}`); } } async makeRequest(query, index) { const url = new URL(this.config.baseUrl + this.config.endpoints.search); url.searchParams.append('q', query); url.searchParams.append('limit', this.resultsPerPage); url.searchParams.append('index', index); const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } renderPagination(container, pagination, onPageChange) { container.innerHTML = ''; // Botón anterior if (pagination.hasPrev) { const prevBtn = document.createElement('button'); prevBtn.className = 'page-btn'; prevBtn.textContent = '‹ Anterior'; prevBtn.onclick = () => onPageChange(this.currentPage - 1); container.appendChild(prevBtn); } // Números de página const startPage = Math.max(0, this.currentPage - 2); const endPage = Math.min(pagination.totalPages - 1, this.currentPage + 2); for (let i = startPage; i <= endPage; i++) { const pageBtn = document.createElement('button'); pageBtn.className = `page-btn ${i === this.currentPage ? 'active' : ''}`; pageBtn.textContent = i + 1; pageBtn.onclick = () => onPageChange(i); container.appendChild(pageBtn); } // Botón siguiente if (pagination.hasNext) { const nextBtn = document.createElement('button'); nextBtn.className = 'page-btn'; nextBtn.textContent = 'Siguiente ›'; nextBtn.onclick = () => onPageChange(this.currentPage + 1); container.appendChild(nextBtn); } } }

🎵 Reproductor de Música Vanilla JS

class MusicPlayer { constructor() { this.currentTrack = null; this.audio = new Audio(); this.isPlaying = false; this.playQueue = []; this.currentIndex = 0; } loadTrack(trackUrl, trackInfo) { this.currentTrack = trackInfo; this.audio.src = trackUrl; // Eventos del reproductor this.audio.addEventListener('loadedmetadata', () => { console.log('Track loaded:', trackInfo.title); }); this.audio.addEventListener('ended', () => { this.playNext(); }); this.audio.addEventListener('error', (e) => { console.error('Error loading track:', e); }); } play() { if (this.audio.src) { this.audio.play() .then(() => { this.isPlaying = true; this.updateUI(); }) .catch(error => { console.error('Playback error:', error); }); } } pause() { this.audio.pause(); this.isPlaying = false; this.updateUI(); } stop() { this.audio.pause(); this.audio.currentTime = 0; this.isPlaying = false; this.updateUI(); } seek(time) { if (this.audio.duration) { this.audio.currentTime = (time / 100) * this.audio.duration; } } setVolume(volume) { this.audio.volume = Math.max(0, Math.min(1, volume)); } playNext() { if (this.currentIndex < this.playQueue.length - 1) { this.currentIndex++; const nextTrack = this.playQueue[this.currentIndex]; this.loadTrack(nextTrack.preview, nextTrack); this.play(); } } playPrevious() { if (this.currentIndex > 0) { this.currentIndex--; const prevTrack = this.playQueue[this.currentIndex]; this.loadTrack(prevTrack.preview, prevTrack); this.play(); } } addToQueue(track) { this.playQueue.push(track); } updateUI() { // Actualizar botones de control const playBtn = document.getElementById('playBtn'); const pauseBtn = document.getElementById('pauseBtn'); if (this.isPlaying) { playBtn.style.display = 'none'; pauseBtn.style.display = 'inline-block'; } else { playBtn.style.display = 'inline-block'; pauseBtn.style.display = 'none'; } // Actualizar información de la canción const trackInfo = document.getElementById('currentTrack'); if (this.currentTrack) { trackInfo.innerHTML = ` ${this.currentTrack.title}
${this.currentTrack.artist} `; } } } // Uso const player = new MusicPlayer(); // Eventos de UI document.getElementById('playBtn').onclick = () => player.play(); document.getElementById('pauseBtn').onclick = () => player.pause(); document.getElementById('stopBtn').onclick = () => player.stop(); document.getElementById('nextBtn').onclick = () => player.playNext(); document.getElementById('prevBtn').onclick = () => player.playPrevious(); // Control de volumen document.getElementById('volumeSlider').oninput = (e) => { player.setVolume(e.target.value / 100); };

🛡️ Manejo de Errores y Rate Limiting

class RobustAPIClient { constructor(config) { this.config = config; this.retryAttempts = 3; this.retryDelay = 1000; // 1 segundo this.rateLimitDelay = 5000; // 5 segundos } async fetchWithRetry(url, options = {}, attempt = 1) { try { const response = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...options.headers } }); // Manejar diferentes códigos de estado if (response.status === 429) { // Rate limiting console.warn('Rate limited, waiting...'); await this.delay(this.rateLimitDelay); return this.fetchWithRetry(url, options, attempt); } if (response.status >= 500) { // Error del servidor, reintentar if (attempt < this.retryAttempts) { console.warn(`Server error, retrying attempt ${attempt + 1}`); await this.delay(this.retryDelay * attempt); return this.fetchWithRetry(url, options, attempt + 1); } } if (!response.ok) { // Otros errores HTTP const errorData = await response.json().catch(() => ({})); throw new APIError( response.status, response.statusText, errorData ); } return await response.json(); } catch (error) { if (error instanceof APIError) { throw error; } // Errores de red u otros if (attempt < this.retryAttempts) { console.warn(`Network error, retrying attempt ${attempt + 1}`); await this.delay(this.retryDelay * attempt); return this.fetchWithRetry(url, options, attempt + 1); } throw new Error(`Failed after ${this.retryAttempts} attempts: ${error.message}`); } } delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Implementación de caché simple async fetchWithCache(url, options = {}) { const cacheKey = `${url}${JSON.stringify(options)}`; const cached = sessionStorage.getItem(cacheKey); if (cached) { const { data, timestamp } = JSON.parse(cached); // Cache por 5 minutos if (Date.now() - timestamp < 300000) { console.log('Returning cached data'); return data; } } const data = await this.fetchWithRetry(url, options); // Guardar en caché sessionStorage.setItem(cacheKey, JSON.stringify({ data, timestamp: Date.now() })); return data; } } class APIError extends Error { constructor(status, statusText, data) { super(`API Error ${status}: ${statusText}`); this.status = status; this.statusText = statusText; this.data = data; } }
← Volver a Tutoriales