Coding, Insights, and Digital Discoveries 👩🏻‍💻

From Stay-at-Home Mom to a Developer After 50

Published on

Building a Feature-Rich To-Do List App with Vanilla JavaScript & PWA Support

todolist

Hey there! I want to share my recent journey of building a Todo app using vanilla JavaScript. After spending a year working with React and Next.js, I felt the need to go back to basics and brush up on my core JavaScript skills. You know how it is - when you work with frameworks for too long, sometimes you need to reconnect with the fundamentals!

📌 Version 1: A Basic To-Do List

I first published a simple version of the To-Do List on GitHub Pages:
👉 Live Demo: My First To-Do List App

This initial version included only basic features:
✅ A task input box with an "Add Task" button.
✅ Newly added tasks appear instantly in the app.
✅ Each task can be deleted.
✅ Tasks are stored in local storage, so they persist after the app is closed and reopened.

At this stage, the app was fully functional but very simple. I wanted to enhance it by adding more interactive features.

🛠️ Enhancing the To-Do List with More Features

As I worked on improving the app, I explored different ways to dynamically generate task elements in JavaScript. This led me to write an article comparing innerHTML vs. createElement() for rendering elements—focusing on security, performance, and maintainability.

I then introduced several key enhancements:

  • Edit capabilities: Users can edit tasks instead of just adding/deleting them.
  • A fresh look using TailwindCSS in vanilla JS project.
  • Checkbox for Completed Tasks: Users can mark tasks as completed, moving them to a separate list.
  • Drag-and-Drop functionality: Users can reorder tasks using drag-and-drop API.
  • Sorting the active tasks by due date or alphabetically.

While working on the completed tasks feature, I had to choose between using for...of and map() for looping through tasks. I ended up going with for...of and wrote about my reasoning in a separate article.

Building the task lists toggle feature led me to learn more about memory management and web performance optimization - you can read about it here.

These updates made the app more user-friendly and dynamic. But I wanted to take it a step further—making it installable as a PWA.

🚀 Making the To-Do List a PWA (Progressive Web App)

A Progressive Web App (PWA) lets a website work just like a native app - you can install it on your device and use it even without internet. Since I often use sticky notes on my laptop for todos, I thought: why not make my todo app installable on both desktop and mobile? After deploying the final version and installing it on my laptop and phone, I found it super convenient for managing tasks. The app feels smooth and intuitive whether I'm adding new todos, checking off completed items, or editing existing tasks. Let me show you how I added the PWA functionality.

👉 Check out my deployed To-Do App.

  1. First, I created a manifest.json file, it tells the browser how the app should behave when installed.
  • ➡️ Create a new file manifest.json:
{
    "name": "My To-Do List",
    "short_name": "To-Do",
    "description": "A simple to-do list with offline support",
    "start_url": "/",
    "display": "standalone",
    "background_color": "#ffffff",
    "theme_color": "#4CAF50",
    "icons": [
        {
            "src": "icons/icon-192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "icons/icon-512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ]
}
  • ➡️ Register manifest.json in index.html:
    Inside the <head> tag, add:
<link rel="manifest" href="manifest.json">

Now, the app can be installed on mobile devices and desktops!

  1. Then, I added a service worker for offline support. A service worker is a script that runs in the background to cache files, allowing the app to work offline and update dynamically.
  • ➡️ Create service-worker.js
const CACHE_NAME = "todo-cache-v1";
const urlsToCache = [
    "index.html",
    "style.css",
    "dist/output.css",
    "script.js",
    "manifest.json",
    "icons/icon-192.png",
    "icons/icon-512.png"
];

// Install Service Worker & Cache Files
self.addEventListener("install", event => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => {
                console.log("Caching static files...");
                return cache.addAll(urlsToCache);
            })
    );
});

// Fetch Cached Files When Offline & Update Cache When Online
self.addEventListener("fetch", event => {
    event.respondWith(
        fetch(event.request)
            .then(response => {
                // Clone response & update cache
                let responseClone = response.clone();
                caches.open(CACHE_NAME).then(cache => {
                    cache.put(event.request, responseClone);
                });
                return response;
            })
            .catch(() => caches.match(event.request)) // If offline, serve cached files
    );
});

// Delete Old Cache & Activate New Service Worker
self.addEventListener("activate", event => {
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(cache => {
                    if (cache !== CACHE_NAME) {
                        return caches.delete(cache);
                    }
                })
            );
        })
    );
});
  • ➡️ Register the Service Worker in script.js
if ("serviceWorker" in navigator) {
    navigator.serviceWorker.register("service-worker.js")
        .then(reg => {
            console.log("Service Worker Registered!", reg);

            reg.onupdatefound = () => {
                const newWorker = reg.installing;
                newWorker.onstatechange = () => {
                    if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
                        showUpdateNotification();
                    }
                };
            };
        })
        .catch(err => console.log("Service Worker Failed!", err));
}

// Show update notification
function showUpdateNotification() {
    const updateBanner = document.createElement("div");
    updateBanner.innerHTML = `
        <div style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
                    background: #333; color: white; padding: 10px 20px; border-radius: 5px;">
            A new update is available. <button id="refreshApp">Update</button>
        </div>
    `;
    document.body.appendChild(updateBanner);
    
    document.getElementById("refreshApp").addEventListener("click", () => {
        location.reload();
    });
}

Now, users get an update prompt when a new version is available!

🚀 Deploying to Netlify (with Auto-Updates)

I'm hosting the app on Netlify, which is great because it automatically deploys whenever I push updates to GitHub. The service worker handles updates smoothly - when there's a new version, users get a notification to refresh the app.

💡 How to Force an Instant Update Using Service Worker?

service-worker.js has a CACHE-NAME defined:

const CACHE_NAME = "todo-cache-v2"; // Increment the version number

After I push the update to GitHub, the Netlify will redeploy the new version, and users will automatically get the new version after refresh the app.

Now, the app is:
✅ Installable on desktops & smartphones
✅ Works offline with cached tasks
✅ Auto-updates when I push new code with more features in the future.

✨ Looking back

This project really helped me reconnect with vanilla JavaScript. Sometimes going back to basics is exactly what I need to become a better developer. Plus, I ended up with a practical app that I actually use daily! It's amazing how a simple todo list helped me brush up on so much about:

  • DOM manipulation
  • Local storage
  • Service workers
  • PWA development
  • Event handling
  • Web performance
  • And even drag-and-drop functionality

The best part is that anyone can install it on their device and use it offline. It's become more than just a learning exercise - it's a tool that people can actually use. Cheer!🍻

← See All Posts