Assignment Chef icon Assignment Chef
All English tutorials

Programming lesson

Building an Album Search and Save Tool: Mastering Arrays, Fetch, and DOM APIs

Learn how to build an album search and save tool using arrays, fetch, DOM APIs, and ES modules. This step-by-step tutorial covers tab navigation, REST API integration, and local persistence.

album search tool JavaScript fetch API DOM manipulation tutorial ES modules example REST API frontend CPSC 1520 assignment tab navigation JavaScript add to my albums persistent data web app Bootstrap tabs async await fetch event delegation JavaScript music app tutorial web development project student coding project album manager app

Introduction: Why Build an Album Search Tool?

In today's music-streaming era, apps like Spotify and Apple Music rely on efficient search and save features. This tutorial walks you through building a similar tool using JavaScript arrays, Fetch API, and DOM manipulation—core skills for any web developer. You'll learn to navigate tabs, fetch album data from a REST API, and manage a personal album collection. Whether you're a student tackling CPSC 1520 or a hobbyist, this guide will level up your frontend skills.

Setting Up Your Project

Before coding, set up the project structure. You'll need two terminals: one for the backend server and one for the frontend.

Backend Setup

Extract the provided assignment-3-backend folder. Run npm install to install dependencies. Check the package.json scripts to start the backend—usually npm start. Leave this terminal running.

Frontend Setup

In a separate terminal, initialize your frontend project. Install Parcel and Bootstrap:

npm init -y
npm install parcel bootstrap

Add scripts to package.json:

"scripts": {
  "start": "parcel index.html",
  "build": "parcel build index.html"
}

In your main.js, import Bootstrap and your custom CSS:

import 'bootstrap/dist/css/bootstrap.min.css';
import '../css/cover.css';

Run npm start to launch the dev server. Pro tip: Avoid including node_modules or parcel-cache in your final submission.

Part 1: Tab Navigation

Tab navigation is like switching between playlists in a music app. We'll use DOM APIs to toggle visibility.

Get the Elements

Select the tab container and individual tabs:

const albumTabNav = document.getElementById('album-tab-navigation');
const searchTab = document.getElementById('search-album-tab');
const myAlbumsTab = document.getElementById('myalbums-tab');
const searchLink = albumTabNav.querySelector('a[href="#search"]');
const myAlbumsLink = albumTabNav.querySelector('a[href="#myalbums"]');

Add Click Event Listener

Listen for clicks on the tab navigation. Use event.target.textContent to determine which tab was clicked.

albumTabNav.addEventListener('click', (event) => {
  const tabName = event.target.textContent.trim();
  if (tabName === 'Search Albums') {
    searchTab.classList.add('active');
    myAlbumsTab.classList.remove('active');
    document.getElementById('search-album-tab').classList.remove('d-none');
    document.getElementById('myalbums-tab').classList.add('d-none');
  } else if (tabName === 'My Albums') {
    myAlbumsTab.classList.add('active');
    searchTab.classList.remove('active');
    document.getElementById('myalbums-tab').classList.remove('d-none');
    document.getElementById('search-album-tab').classList.add('d-none');
  }
});

This pattern is similar to how AI chatbots switch between conversation modes. The d-none class from Bootstrap hides the inactive panel.

Part 2: Search Albums with REST API

Now we integrate the backend. We'll create a function in api/album.js to fetch albums by name.

Create the Fetch Function

Use the Fetch API to call the backend endpoint. The backend runs on http://localhost:3000 (check README).

// api/album.js
export async function searchAlbums(query) {
  const response = await fetch(`http://localhost:3000/api/albums?q=${encodeURIComponent(query)}`);
  if (!response.ok) {
    throw new Error('Failed to fetch albums');
  }
  return response.json();
}

Create DOM Elements

In dom/albumElements.js, build a function to render each album as a list item.

// dom/albumElements.js
export function createAlbumListItem(album) {
  const li = document.createElement('li');
  li.className = 'list-group-item d-flex justify-content-between align-items-center';
  li.innerHTML = `
    ${album.title} by ${album.artist}
    Add to My Albums
  `;
  return li;
}

Handle Form Submission

In main.js, listen for the search form submission.

import { searchAlbums } from './api/album.js';
import { createAlbumListItem } from './dom/albumElements.js';

document.getElementById('search-album-form').addEventListener('submit', async (event) => {
  event.preventDefault();
  const query = document.getElementById('search-input').value;
  const albumList = document.getElementById('searched-albums');
  albumList.innerHTML = ''; // Clear previous results

  try {
    const albums = await searchAlbums(query);
    albums.forEach(album => {
      const li = createAlbumListItem(album);
      albumList.appendChild(li);
    });
  } catch (error) {
    console.error('Search failed:', error);
  }
});

This is similar to how Spotify's search instantly fetches results from their API. Notice we clear the list before adding new results—this resets the state as required.

Part 3: Add to My Albums

Now we enable saving albums to a personal list. When the user clicks "Add to My Albums," the album moves from search results to the "My Albums" tab.

Event Delegation for Buttons

Since the add buttons are dynamically created, use event delegation on the album list container.

document.getElementById('searched-albums').addEventListener('click', (event) => {
  if (event.target.classList.contains('add-btn')) {
    const li = event.target.closest('li');
    const albumData = {
      title: li.querySelector('span').textContent.split(' by ')[0],
      artist: li.querySelector('span').textContent.split(' by ')[1]
    };
    addToMyAlbums(albumData);
    li.remove(); // Remove from search results
  }
});

function addToMyAlbums(album) {
  const myAlbumsList = document.getElementById('my-albums-list');
  const li = document.createElement('li');
  li.className = 'list-group-item';
  li.textContent = `${album.title} by ${album.artist}`;
  myAlbumsList.appendChild(li);
}

Duplicates are allowed, so no duplicate check needed. This mimics how you might save songs to a playlist in Apple Music.

Bonus: Persist Albums with REST API

For extra credit, save albums to the backend so they persist on page reload. You'll need to add a POST endpoint in the backend's api.js.

Backend Endpoint (api.js)

// In assignment-3-backend/api.js
app.post('/api/albums/save', (req, res) => {
  const album = req.body;
  // Read existing database, append album, write back
  const db = JSON.parse(fs.readFileSync('./albums_app_db.json'));
  db.myAlbums.push(album);
  fs.writeFileSync('./albums_app_db.json', JSON.stringify(db, null, 2));
  res.status(201).json({ message: 'Album saved' });
});

Frontend Save Function

Modify addToMyAlbums to also POST data.

async function addToMyAlbums(album) {
  // POST to backend
  await fetch('http://localhost:3000/api/albums/save', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(album)
  });
  // Update UI
  const myAlbumsList = document.getElementById('my-albums-list');
  const li = document.createElement('li');
  li.className = 'list-group-item';
  li.textContent = `${album.title} by ${album.artist}`;
  myAlbumsList.appendChild(li);
}

Load Saved Albums on Page Load

When the app starts, fetch saved albums from the backend.

async function loadSavedAlbums() {
  const response = await fetch('http://localhost:3000/api/albums/saved');
  const albums = await response.json();
  const myAlbumsList = document.getElementById('my-albums-list');
  albums.forEach(album => {
    const li = document.createElement('li');
    li.className = 'list-group-item';
    li.textContent = `${album.title} by ${album.artist}`;
    myAlbumsList.appendChild(li);
  });
}

loadSavedAlbums();

This bonus part turns your app into a persistent album manager, similar to how Notion saves your database entries.

Common Pitfalls and Tips

  • Backend not running: Ensure the backend server is active before testing search. Use two terminals.
  • CORS issues: If the backend blocks requests, add cors middleware or use a proxy in Parcel.
  • Duplicate event listeners: Avoid attaching listeners inside loops. Use event delegation.
  • State reset: Always clear the search list before displaying new results.
  • Asynchronous errors: Wrap fetch calls in try-catch to handle network failures gracefully.

Conclusion

You've built a fully functional album search and save tool using arrays and loops, fetch fundamentals, DOM APIs and timers, and ES modules. These skills are transferable to building AI-powered recommendation systems, e-commerce product searches, or social media feeds. Keep experimenting—try adding delete functionality or sorting albums by year. Happy coding!