Command-Line To-Do App

Build a command-line to-do app in Python step by step: classes, file handling and saving your tasks so they survive a restart.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Intermediate Project — Learn File Handling, Data Persistence, and OOP

🧠 Project Overview

A To-Do List app is one of the most essential beginner–intermediate Python projects. It teaches file management, user input handling, and object-oriented programming (OOP).

In real life, the same logic is used in productivity software like Todoist or Microsoft To Do — just with a graphical interface instead of command line.

🎯 Goal

Build a command-line To-Do app that can:

🧩 Core Concepts

💡 Technologies & Concepts Used

FeatureDescription
open()File reading/writing
json moduleStructured storage
datetimeFor due dates & reminders
coloramaCLI color highlighting
osCheck and manage file existence

⚙️ Starter Code

This is a command-line app that uses input() for user interaction. The browser demo below shows a simplified version. For the full interactive experience, copy the code below and run it in your local Python environment (IDLE, VS Code, or terminal). Jump to the Browser Demo.

Here's a functional starting version of your app. Copy it to your local Python editor and run it.

import json
from datetime import datetime

class Task:
    def __init__(self, title, priority="Medium", category="General", completed=False, created=None, due=None):
        self.title = title
        self.priority = priority
        self.category = category
        self.completed = completed
        self.created = created or datetime.now().strftime("%Y-%m-%d %H:%M")
        self.due = due

    def mark_done(self):
        self.completed = True

    def to_dict(self):
        return self.__dict__

class TodoApp:
    def __init__(self, filename="tasks.json"):
        self.filename = filename
        self.tasks = self.load_tasks()

    def load_tasks(self):
        try:
            with open(self.filename, "r") as f:
                data = json.load(f)
                return [Task(**t) for t in data]
        except (FileNotFoundError, json.JSONDecodeError):
            return []

    def save_tasks(self):
        with open(self.filename, "w") as f:
            json.dump([t.to_dict() for t in self.tasks], f, indent=4)

    def add_task(self):
        title = input("Task title: ")
        if not title.strip():
            print("❌ Title cannot be empty.")
            return
        priority = input("Priority (High/Medium/Low): ") or "Medium"
        category = input("Category: ") or "General"
        due = input("Due date (YYYY-MM-DD, optional): ") or None
        task = Task(title, priority, category, False, None, due)
        self.tasks.append(task)
        self.save_tasks()
        print(f"✅ Task '{title}' added.")

    def list_tasks(self):
        if not self.tasks:
            print("📭 No tasks found.")
            return
        for i, t in enumerate(self.tasks, 1):
            status = "✔️" if t.completed else "❌"
            print(f"{i}. {t.title} [{t.priority}] ({t.category}) - {status}")

    def mark_complete(self):
        self.list_tasks()
        try:
            index = int(input("Enter task number to mark complete: "))
            self.tasks[index-1].mark_done()
            self.save_tasks()
            print("✅ Task marked complete!")
        except (ValueError, IndexError):
            print("❌ Invalid selection.")

    def delete_task(self):
        self.list_tasks()
        try:
            index = int(input("Enter task number to delete: "))
            task = self.tasks.pop(index-1)
            self.save_tasks()
            print(f"🗑️ Deleted '{task.title}'.")
        except (ValueError, IndexError):
            print("❌ Invalid number.")

    def search_tasks(self):
        term = input("Search keyword: ").lower()
        results = [t for t in self.tasks if term in t.title.lower() or term in t.category.lower()]
        if results:
            print(f"🔍 Found {len(results)} result(s):")
            for t in results:
                print(f"- {t.title} ({t.category}) [{t.priority}]")
        else:
            print("No matches found.")

    def menu(self):
        while True:
            print("\n--- TO-DO MENU ---")
            print("1. Add Task")
            print("2. List Tasks")
            print("3. Mark Complete")
            print("4. Delete Task")
            print("5. Search")
            print("6. Exit")
            choice = input("Choose option: ")

            if choice == "1": self.add_task()
            elif choice == "2": self.list_tasks()
            elif choice == "3": self.mark_complete()
            elif choice == "4": self.delete_task()
            elif choice == "5": self.search_tasks()
            elif choice == "6":
                print("👋 Goodbye!")
                break
            else:
                print("❌ Invalid choice.")
                
if __name__ == "__main__":
    TodoApp().menu()

🚀 How to Run Locally:

🎮 Browser Demo (Simplified Version)

This demo version shows the core functionality without user input (browser limitation):

import json
from datetime import datetime

class Task:
    def __init__(self, title, priority="Medium", category="General", completed=False):
        self.title = title
        self.priority = priority
        self.category = category
        self.completed = completed
        self.created = datetime.now().strftime("%Y-%m-%d %H:%M")

    def mark_done(self):
        self.completed = True

    def __str__(self):
        status = "✔️" if self.completed else "❌"
        return f"{self.title} [{self.priority}] ({self.category}) - {status}"

# Demo: Create and manage tasks
print("📝 TO-DO APP DEMO\n")

# Create some tasks
tasks = []
tasks.append(Task("Learn Python OOP", "High", "Education"))
tasks.append(Task("Build a project", "High", "Coding"))
tasks.append(Task("Practice decorators", "Medium", "Education"))
tasks.append(Task("Go for a walk", "Low", "Health"))

# Display all tasks
print("📋 All Tasks:")
for i, task in enumerate(tasks, 1):
    print(f"  {i}. {task}")

# Mark a task complete
print("\n✅ Marking task #1 complete...")
tasks[0].mark_done()

# Display updated list
print("\n📋 Updated Tasks:")
for i, task in enumerate(tasks, 1):
    print(f"  {i}. {task}")

# Filter by priority
print("\n🔥 High Priority Tasks:")
high_priority = [t for t in tasks if t.priority == "High"]
for task in high_priority:
    print(f"  - {task}")

# Show statistics
completed = sum(1 for t in tasks if t.completed)
print(f"\n📊 Progress: {completed}/{len(tasks)} tasks completed!")

print("\n✨ Demo complete! Run the full version locally for interactive menu.")

💬 Expected Output (Full Version)

--- TO-DO MENU ---
1. Add Task
2. List Tasks
3. Mark Complete
4. Delete Task
5. Search
6. Exit
Choose option: 1
Task title: Study Python
Priority: High
Category: Study
✅ Task 'Study Python' added.

🚀 Enhancement Ideas

1️⃣ Add Colors (User Interface)

Install colorama to make CLI text colorful:

pip install colorama
from colorama import Fore, Style
print(Fore.GREEN + "✔ Completed" + Style.RESET_ALL)
print(Fore.RED + "❌ Pending" + Style.RESET_ALL)

This simple change boosts readability — important for apps used daily.

2️⃣ JSON Storage (Structured & Expandable)

Instead of plain text lines, use JSON to save metadata:

{
  "title": "Study Python",
  "priority": "High",
  "category": "Education",
  "completed": false,
  "created": "2025-11-09 20:14",
  "due": "2025-11-15"
}

You can then sort, filter, and search efficiently — similar to a small database.

3️⃣ Due Dates & Notifications

Use datetime and timedelta to detect overdue tasks:

from datetime import datetime
due_date = datetime.strptime(task.due, "%Y-%m-%d")
if due_date < datetime.now():
    print("⚠️ Overdue!")

Later, integrate plyer.notification for desktop popups or even email reminders with smtplib.

4️⃣ Priority Levels & Sorting

Store priority as integers (1=High, 2=Medium, 3=Low), then sort automatically:

sorted_tasks = sorted(self.tasks, key=lambda t: {"High":1,"Medium":2,"Low":3}[t.priority])

5️⃣ Categories & Filters

Let users list tasks by category:

cat = input("Enter category: ")
for t in self.tasks:
    if t.category.lower() == cat.lower():
        print(t.title)

Organizing by category is essential for productivity — exactly how project management tools work internally.

6️⃣ Validation and Error Recovery

Wrap critical operations with try/except and confirmations:

try:
    index = int(input("Enter index: "))
    if index not in range(1, len(self.tasks)+1):
        raise ValueError
except ValueError:
    print("❌ Please enter a valid task number.")

7️⃣ CLI Menus and Keyboard Shortcuts

Add quick commands like: a → Add task, l → List all, m → Mark complete. This improves speed for experienced users.

8️⃣ Auto Backup

Each time the app saves, also write a backup:

import shutil
shutil.copy("tasks.json", "tasks_backup.json")

9️⃣ Analytics / Statistics

Calculate: Completed vs pending tasks, Average task age, Most common category

done = sum(t.completed for t in self.tasks)
print(f"Progress: {done}/{len(self.tasks)} tasks done!")

🔟 Simple Encryption

To keep personal tasks safe:

import base64
def encrypt(data): return base64.b64encode(data.encode()).decode()
def decrypt(data): return base64.b64decode(data).decode()

Use this before saving JSON — basic, but effective for privacy.

💡 NEXT STEPS — From Intermediate to Advanced

To evolve this into a professional-level tool, start introducing modular architecture and optional GUI.

🧱 1. Modularize Code

Split into separate files:

todo/
 ┣ main.py
 ┣ models.py (Task class)
 ┣ storage.py (save/load)
 ┣ utils.py (helpers)
 ┗ menu.py

This teaches maintainable structure used in all professional codebases.

🧠 2. Add CLI Arguments (sys / argparse)

Let users run commands directly:

python todo.py --add "Buy groceries"
python todo.py --list

Use Python's argparse library:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--add")
args = parser.parse_args()

📦 3. Add SQLite Database

Instead of JSON, use SQLite for instant data retrieval:

import sqlite3
conn = sqlite3.connect("tasks.db")

Then map each task into a table with SQL CRUD operations — identical to backend app logic.

💬 4. Add Natural Language Input

Use simple parsing: "Add Buy milk tomorrow at 5pm"

Then interpret "tomorrow" using libraries like dateparser to auto-create due dates.

🪄 5. Add Notifications or Email Reminders

This makes your CLI feel like a background productivity daemon.

🌐 6. Connect to Cloud Storage (Advanced)

Save tasks online using Google Drive API, Firebase, or Supabase so you can access them anywhere. This brings your Python CLI closer to a SaaS-level product.

🧑‍💻 7. Build GUI Version

Turn this into a desktop app:

This is how you move from console → app development.

📱 8. Export to Mobile

Convert the same logic into a React Native or Flutter app with shared logic from your Python backend.

🧩 9. Integrate AI Suggestions

Let an AI assistant prioritize or label tasks automatically by reading their text:

"Study Python OOP" → category = Education, priority = Medium

This can use OpenAI or your future FlickAI when ready.

🧾 10. Monetize It

Even a CLI can be monetized through GitHub Sponsors or Gumroad templates.

⚙️ Super Advanced Tips for Expert Coders

✅ Next Steps

Each new feature is a learning milestone — by finishing this project, you'll understand how real developers structure, persist, and maintain applications.

Related lessons