Final Project: Mini SPA (Single Page Application)

Goal of this Project

End this course with a project you can showcase in your portfolio. You will:

Step 1: Structure the SPA

Create basic HTML with container for content:


<nav>
  <button data-page="home">Home</button>
  <button data-page="tasks">Tasks</button>
</nav>

<div id="app"></div>

Step 2: Routing

Switch views without reloading:


const app = document.getElementById("app");

function navigate(page) {
  if(page === "home") {
    app.innerHTML = "<h2>Welcome to Mini SPA</h2>";
  } else if(page === "tasks") {
    renderTasks();
  }
}

document.querySelectorAll("nav button").forEach(btn => {
  btn.addEventListener("click", () => navigate(btn.dataset.page));
});

navigate("home");

Step 3: Fetch Data from API


async function fetchTasks() {
  try {
    const res = await fetch("https://jsonplaceholder.typicode.com/todos?_limit=5");
    const data = await res.json();
    state.tasks = data;
    renderTasks();
  } catch(err) {
    console.error("Error fetching tasks:", err);
  }
}

Step 4: State Management


const state = {
  tasks: []
};

Step 5: Render Tasks


function renderTasks() {
  app.innerHTML = `
    <h2>Tasks</h2>
    <ul>
      ${state.tasks.map(t => `
        <li>${t.title} 
          <button class="delete" data-id="${t.id}">Delete</button>
        </li>
      `).join("")}
    </ul>
    <input id="newTask" placeholder="New task"/>
    <button id="addTask">Add</button>
  `;

  document.querySelectorAll(".delete").forEach(btn => {
    btn.addEventListener("click", () => deleteTask(btn.dataset.id));
  });

  document.getElementById("addTask").addEventListener("click", addTask);
}

Step 6: Add, Delete, Update


function addTask() {
  const input = document.getElementById("newTask");
  if(input.value.trim() === "") return;
  
  const newTask = {
    id: Date.now(),
    title: input.value
  };
  
  state.tasks.push(newTask);
  input.value = "";
  renderTasks();
}

function deleteTask(id) {
  state.tasks = state.tasks.filter(t => t.id != id);
  renderTasks();
}

Step 7: Optional LocalStorage Caching


function saveToLocalStorage() {
  localStorage.setItem("tasks", JSON.stringify(state.tasks));
}

function loadFromLocalStorage() {
  const data = localStorage.getItem("tasks");
  if(data) state.tasks = JSON.parse(data);
}

// Call load on startup
loadFromLocalStorage();
renderTasks();

// Update save on changes
document.addEventListener("click", saveToLocalStorage);

Practice

Try creating multiple pages, dynamically adding tasks, and storing them in LocalStorage.

Task