Loading...
Loading...
CRUD mastery. Create, read, update, delete — with persistence and filtering.
Module 11 · JavaScript Projects
Theory becomes skill through building. These guided projects — calculator, todo app, weather app, and more — combine everything you've learned into real applications you can extend, share, and put in a portfolio.
Learn Todo App in JavaScript — covering Architecture, Data Model, Rendering, CRUD Operations with interactive visual examples and step-by-step explanations.
Reading about JavaScript is not the same as building with it. These guided projects combine variables, functions, DOM manipulation, async fetch, and state management into applications you can run, extend, and showcase.
Each project walks through architecture decisions, common pitfalls, and incremental steps. By the end you will have portfolio-ready apps and the confidence to start your own projects from scratch.
Understanding todo app is essential for every JavaScript developer. It shows up in frontend UI code, backend APIs, and framework internals — and getting it wrong leads to bugs that are hard to trace. This chapter builds intuition with visuals so the behavior sticks.
Separate data, logic, and rendering cleanly.
// Architecture: Model → Controller → View // // Model: Array of todo objects (the data) // Controller: Functions that modify the data // View: Functions that render the DOM // // Flow: User action → update model → re-render view // // Project structure: // todo-app/ // ├── index.html // ├── style.css // └── app.js // HTML skeleton: // <div class="app"> // <h1>Todos</h1> // <form class="todo-form"> // <input type="text" placeholder="What needs to be done?"> // <button type="submit">Add</button> // </form> // <div class="filters"> // <button data-filter="all" class="active">All</button> // <button data-filter="active">Active</button> // <button data-filter="completed">Completed</button> // </div> // <ul class="todo-list"></ul> // <div class="footer"> // <span class="count"></span> // <button class="clear-completed">Clear completed</button> // </div> // </div>
Each todo is an object with id, text, and completed status.
// app.js — Data layer: let todos = []; let currentFilter = "all"; // Todo shape: // { // id: "a1b2c3", — unique identifier // text: "Buy groceries", — the task description // completed: false, — toggle state // createdAt: 1704067200 — timestamp for ordering // } function createTodo(text) { return { id: crypto.randomUUID(), text: text.trim(), completed: false, createdAt: Date.now(), }; } // Filtered views (never mutate the source array): function getFilteredTodos() { switch (currentFilter) { case "active": return todos.filter(t => !t.completed); case "completed": return todos.filter(t => t.completed); default: return todos; } } function getActiveTodoCount() { return todos.filter(t => !t.completed).length; }
One render function rebuilds the list from data.
// DOM references: const form = document.querySelector(".todo-form"); const input = form.querySelector("input"); const list = document.querySelector(".todo-list"); const countEl = document.querySelector(".count"); const filterButtons = document.querySelectorAll("[data-filter]"); // The single render function: function render() { const filtered = getFilteredTodos(); // Rebuild the list: list.innerHTML = filtered.map(todo => ` <li class="todo-item ${todo.completed ? "completed" : ""}" data-id="${todo.id}"> <label class="checkbox"> <input type="checkbox" ${todo.completed ? "checked" : ""}> <span class="checkmark"></span> </label> <span class="todo-text">${escapeHtml(todo.text)}</span> <button class="edit-btn" aria-label="Edit">✎</button> <button class="delete-btn" aria-label="Delete">×</button> </li> `).join(""); // Update counter: const count = getActiveTodoCount(); countEl.textContent = `${count} item${count !== 1 ? "s" : ""} left`; // Update filter buttons: filterButtons.forEach(btn => { btn.classList.toggle("active", btn.dataset.filter === currentFilter); }); // Save to localStorage: saveTodos(); } // XSS prevention — ALWAYS escape user content: function escapeHtml(str) { const div = document.createElement("div"); div.textContent = str; return div.innerHTML; }
Create, toggle, edit, delete, and filter.
// CREATE — form submit: form.addEventListener("submit", (e) => { e.preventDefault(); const text = input.value.trim(); if (!text) return; todos.push(createTodo(text)); input.value = ""; render(); }); // DELETE & TOGGLE — event delegation on the list: list.addEventListener("click", (e) => { const item = e.target.closest(".todo-item"); if (!item) return; const id = item.dataset.id; // Delete: if (e.target.closest(".delete-btn")) { todos = todos.filter(t => t.id !== id); render(); return; } // Toggle complete: if (e.target.closest(".checkbox")) { const todo = todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; render(); } });
Create adds to the array. Delete filters it out. Toggle flips the completed boolean. All end with render().
Save to localStorage so todos survive page reload.
const STORAGE_KEY = "todo-app-data"; function saveTodos() { const data = JSON.stringify({ todos, currentFilter }); localStorage.setItem(STORAGE_KEY, data); } function loadTodos() { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return; const data = JSON.parse(raw); // Validate structure before using: if (Array.isArray(data.todos)) { todos = data.todos.filter(t => typeof t.id === "string" && typeof t.text === "string" && typeof t.completed === "boolean" ); } if (["all", "active", "completed"].includes(data.currentFilter)) { currentFilter = data.currentFilter; } } catch { // Corrupted data — start fresh todos = []; } } // Initialize: loadTodos(); render(); // Key patterns in this project: // ✓ CRUD operations on an array // ✓ Data-driven rendering (render from state, not DOM) // ✓ Event delegation for dynamic elements // ✓ XSS prevention (escapeHtml) // ✓ localStorage with validation // ✓ Defensive parsing (try/catch, type checks) // ✓ Immutable updates where possible (filter creates new array)
Common questions about todo app.