DEV Community

Cover image for Creating a To-Do List App with Firebase Firestore
Javier Andre Neira Machaca
Javier Andre Neira Machaca

Posted on

Creating a To-Do List App with Firebase Firestore

In this post, I will show you how to create a to-do list application using HTML, CSS, JavaScript, and Firebase Firestore. This guide will help you understand how to integrate a cloud database with a simple web application, providing a complete experience from setup to implementation.

Technologies We Will Use:

  • HTML/CSS: For the structure and design of the application, ensuring that the interface is intuitive and easy to use.
  • JavaScript: For the application logic, adding interactivity and communicating with Firebase. This includes manipulating the DOM, handling events, and performing CRUD (Create, Read, Update, Delete) operations.
  • Firebase Firestore: As our cloud database to store tasks. Firestore allows us to store structured data and access it in real time.

Project Structure
Our project has the following file structure:

firebase-todo-app/
|
├── src/
│ ├── css/
│ │ └── styles.css
│ └── js/
│ └── app.js

├── index.html
└── README.md

  • index.html is the main file that loads in the browser and contains the base structure of the application.
  • styles.css contains the styles that make our application look good and visually appealing.
  • app.js is where all the JavaScript logic for handling tasks resides, including integration with Firebase Firestore.

Step 1: Firebase Setup
Before starting with the code, you need to configure Firebase. You can create a new project from Firebase Console, enable Firestore Database, and copy the project configuration.

Then, in the app.js file, add the Firebase configuration:

const firebaseConfig = {
    apiKey: "YOUR_API_KEY",
    authDomain: "YOUR_AUTH_DOMAIN",
    projectId: "YOUR_PROJECT_ID",
    storageBucket: "YOUR_STORAGE_BUCKET",
    messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
    appId: "YOUR_APP_ID"
};

firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
Enter fullscreen mode Exit fullscreen mode

With this, we have connected our application to Firebase Firestore.

Step 2: Creating the User Interface
In our index.html file, we have a simple structure that includes:

  • A title (<h1>) for the application.
  • A dropdown () to filter tasks (all, completed, pending).
  • A container () to display our tasks.
  • An input field () and a button () to add new tasks.
  • A container for messages () where we will display status messages for the user.

    Here is the HTML code:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Firebase To-Do App</title>
        <link rel="stylesheet" href="css/styles.css">
    </head>
    <body>
        <h1>Firebase To-Do List</h1>
        <select id="filter">
            <option value="all">All</option>
            <option value="completed">Completed</option>
            <option value="pending">Pending</option>
        </select>
        <div id="todo-container">
            <p>Loading tasks...</p> <!-- Initial loading message -->
        </div>
        <input type="text" id="todo-input" placeholder="New task...">
        <button id="add-button">Add Task</button>
        <!-- Container for messages -->
        <div id="message"></div>
    
        <!-- Load Firebase from CDN (compat version) -->
        <script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-app-compat.js"></script>
        <script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-firestore-compat.js"></script>
    
        <!-- Your JavaScript file -->
        <script src="js/app.js"></script>
    </body>
    </html>
    

    Step 3: Styling with CSS
    The styles.css file allows us to give a pleasant visual style to the application. Here are some of the styles we applied:

    General Design: We use a sans-serif font to give the application a modern and clean look.

    • Task Container: The container (#todo-container) has rounded borders, shadow, and a white background to stand out against the general background. This helps keep tasks organized and well defined.
    • Buttons: The "Add Task" button has a green background that changes when hovered over to indicate that it is active and to invite the user to interact with it.

    Step 4: JavaScript Logic
    In the app.js file, we define the logic for adding, displaying, editing, and deleting tasks. Firebase allows us to store tasks in the database and listen for changes in real time.

    Adding a Task

    To add a new task, we use the addTodo() function that takes the value from the input field and saves it in Firestore. Additionally, we ask the user if they want to add an optional due date:

    async function addTodo() {
        const todoInput = document.getElementById("todo-input").value;
        const dueDateInput = prompt("Due date (optional): YYYY-MM-DD");
        const messageDiv = document.getElementById("message");
    
        if (todoInput) {
            try {
                const createdAt = new Date();
                const dueDate = dueDateInput ? new Date(dueDateInput) : null;
    
                await db.collection("todos").add({ text: todoInput, completed: false, createdAt, dueDate });
                document.getElementById("todo-input").value = "";
                showMessage("Task added successfully!", "green");
            } catch (error) {
                showMessage("Error adding task: " + error.message, "red");
            }
        } else {
            showMessage("Please enter a task.", "red");
        }
    }
    

    In this code:

    • We validate that the input field is not empty.
    • We create an object with the creation date and optionally a due date.
    • We use db.collection("todos").add() to save the task in Firestore.
    • We display a success or error message using the showMessage() function.
    • Displaying Tasks in Real Time

    The loadTodos() function listens for changes in the database using onSnapshot() and displays all tasks in the interface. This function also allows filtering tasks by their status:

    function loadTodos(filter = "all") {
        let query = db.collection("todos");
    
        if (filter === "completed") {
            query = query.where("completed", "==", true);
        } else if (filter === "pending") {
            query = query.where("completed", "==", false);
        }
    
        query.onSnapshot((snapshot) => {
            const todoContainer = document.getElementById("todo-container");
            todoContainer.innerHTML = "";
            snapshot.forEach((doc) => {
                const taskData = doc.data();
                const taskId = doc.id;
    
                const task = document.createElement("div");
                task.textContent = taskData.text;
    
                // Display creation and due dates
                if (taskData.createdAt) {
                    const createdDate = new Date(taskData.createdAt.seconds * 1000);
                    task.textContent += ` (Created: ${createdDate.toLocaleDateString()})`;
                }
    
                if (taskData.dueDate) {
                    const dueDate = new Date(taskData.dueDate.seconds * 1000);
                    task.textContent += ` (Due: ${dueDate.toLocaleDateString()})`;
                }
    
                if (taskData.completed) {
                    task.style.textDecoration = "line-through";
                    task.style.color = "gray";
                }
    
                const completeButton = document.createElement("button");
                completeButton.textContent = "✔️";
                completeButton.style.marginLeft = "10px";
                completeButton.onclick = () => markAsCompleted(taskId);
    
                const editButton = document.createElement("button");
                editButton.textContent = "✏️";
                editButton.style.marginLeft = "5px";
                editButton.onclick = () => editTask(taskId, taskData.text);
    
                const deleteButton = document.createElement("button");
                deleteButton.textContent = "🗑️";
                deleteButton.style.marginLeft = "5px";
                deleteButton.onclick = () => deleteTask(taskId);
    
                task.appendChild(completeButton);
                task.appendChild(editButton);
                task.appendChild(deleteButton);
                todoContainer.appendChild(task);
            });
        });
    }
    

    In this code:

    • We filter tasks by status (completed, pending, or all).
    • We listen for changes in real time with onSnapshot() to keep the interface automatically updated.
    • We dynamically create HTML elements to display each task, along with buttons to complete, edit, and delete.

    Final Thoughts
    Creating a to-do list application with Firebase is an excellent way to learn how to work with cloud databases and understand how to build a complete application with HTML, CSS, and JavaScript. Throughout this project, we explored how to add, edit, delete, and display tasks using Firestore, which provides real-time synchronization so users always have updated data.
    Firebase makes it easy to implement many of the features needed for modern applications, such as cloud data persistence and real-time updates. With the right structure and development best practices, this project can be extended to add more advanced features like user authentication, notifications, or file storage.
    I hope this guide helps you learn and develop your own applications with Firebase. If you have any questions or suggestions, feel free to leave a comment!
    https://github.com/Dray-Lexter/firebase-todo-app.git

Top comments (0)