Chapter 20 - Build Your First To-Do App: A Step-by-Step Guide for Beginner Developers

Building a to-do app teaches full-stack development basics. It involves data storage, server-side logic, and client-side rendering. Start simple, add features gradually, and test thoroughly for a solid learning experience.

Chapter 20 - Build Your First To-Do App: A Step-by-Step Guide for Beginner Developers

Let’s dive into building a simple to-do app! It’s a great project to get your feet wet with app development. I remember when I first tackled this - it seemed daunting, but it’s actually pretty straightforward once you break it down.

First things first, we need to plan out our app. What do we want it to do? At its core, a to-do app should let users add tasks, mark them as complete, and delete them. We might also want to add features like due dates or priority levels, but let’s keep it simple for now.

For this project, we’ll use Python with Flask for the backend and some basic HTML/CSS/JavaScript for the frontend. Don’t worry if you’re not familiar with all of these - we’ll go through it step by step.

Let’s start with setting up our environment. Make sure you have Python installed, then create a new directory for your project. Open up your terminal and run:

mkdir todo_app
cd todo_app
python -m venv venv
source venv/bin/activate  # On Windows, use `venv\Scripts\activate`
pip install flask

This creates a new virtual environment and installs Flask. Now, let’s create our main app file. In your project directory, create a file called app.py:

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

todos = []

@app.route('/')
def index():
    return render_template('index.html', todos=todos)

@app.route('/add', methods=['POST'])
def add():
    todo = request.form['todo']
    todos.append(todo)
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)

This sets up our basic Flask app with two routes: one to display our todos, and one to add new ones. We’re using a simple list to store our todos for now, but in a real app, you’d want to use a database.

Next, we need to create our HTML template. Make a new directory called templates and create a file called index.html inside it:

<!DOCTYPE html>
<html>
<head>
    <title>Todo App</title>
</head>
<body>
    <h1>Todo List</h1>
    <form action="{{ url_for('add') }}" method="post">
        <input type="text" name="todo">
        <button type="submit">Add Todo</button>
    </form>
    <ul>
    {% for todo in todos %}
        <li>{{ todo }}</li>
    {% endfor %}
    </ul>
</body>
</html>

This creates a simple form to add todos and a list to display them. Flask’s templating engine allows us to loop through our todos and display them dynamically.

Now, let’s run our app! In your terminal, type:

python app.py

Visit http://localhost:5000 in your browser, and you should see your todo app!

But wait, we’re not done yet. Our app is pretty bare-bones right now. Let’s add the ability to mark todos as complete and delete them. We’ll need to modify our app.py:

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

todos = []

@app.route('/')
def index():
    return render_template('index.html', todos=todos)

@app.route('/add', methods=['POST'])
def add():
    todo = request.form['todo']
    todos.append({'task': todo, 'done': False})
    return redirect(url_for('index'))

@app.route('/complete/<int:index>')
def complete(index):
    todos[index]['done'] = True
    return redirect(url_for('index'))

@app.route('/delete/<int:index>')
def delete(index):
    del todos[index]
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)

And update our index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Todo App</title>
    <style>
        .done {
            text-decoration: line-through;
        }
    </style>
</head>
<body>
    <h1>Todo List</h1>
    <form action="{{ url_for('add') }}" method="post">
        <input type="text" name="todo">
        <button type="submit">Add Todo</button>
    </form>
    <ul>
    {% for todo in todos %}
        <li class="{{ 'done' if todo['done'] else '' }}">
            {{ todo['task'] }}
            {% if not todo['done'] %}
                <a href="{{ url_for('complete', index=loop.index0) }}">Complete</a>
            {% endif %}
            <a href="{{ url_for('delete', index=loop.index0) }}">Delete</a>
        </li>
    {% endfor %}
    </ul>
</body>
</html>

Now we’ve got a more functional app! You can add todos, mark them as complete, and delete them. The completed todos get a line-through style to visually indicate they’re done.

But you know what would make this even cooler? Some JavaScript to make it feel more dynamic. Let’s add the ability to add todos without reloading the page. We’ll use the Fetch API for this.

Update your index.html again:

<!DOCTYPE html>
<html>
<head>
    <title>Todo App</title>
    <style>
        .done {
            text-decoration: line-through;
        }
    </style>
</head>
<body>
    <h1>Todo List</h1>
    <form id="todo-form">
        <input type="text" id="todo-input">
        <button type="submit">Add Todo</button>
    </form>
    <ul id="todo-list">
    {% for todo in todos %}
        <li class="{{ 'done' if todo['done'] else '' }}">
            {{ todo['task'] }}
            {% if not todo['done'] %}
                <a href="{{ url_for('complete', index=loop.index0) }}">Complete</a>
            {% endif %}
            <a href="{{ url_for('delete', index=loop.index0) }}">Delete</a>
        </li>
    {% endfor %}
    </ul>

    <script>
        document.getElementById('todo-form').addEventListener('submit', function(e) {
            e.preventDefault();
            var input = document.getElementById('todo-input');
            fetch('/add', {
                method: 'POST',
                body: new URLSearchParams({
                    'todo': input.value
                })
            })
            .then(response => response.text())
            .then(html => {
                document.getElementById('todo-list').innerHTML = html;
                input.value = '';
            });
        });
    </script>
</body>
</html>

And update your app.py one more time:

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

todos = []

@app.route('/')
def index():
    return render_template('index.html', todos=todos)

@app.route('/add', methods=['POST'])
def add():
    todo = request.form['todo']
    todos.append({'task': todo, 'done': False})
    return render_template('todo_list.html', todos=todos)

@app.route('/complete/<int:index>')
def complete(index):
    todos[index]['done'] = True
    return redirect(url_for('index'))

@app.route('/delete/<int:index>')
def delete(index):
    del todos[index]
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)

You’ll also need to create a new file todo_list.html in your templates folder:

{% for todo in todos %}
    <li class="{{ 'done' if todo['done'] else '' }}">
        {{ todo['task'] }}
        {% if not todo['done'] %}
            <a href="{{ url_for('complete', index=loop.index0) }}">Complete</a>
        {% endif %}
        <a href="{{ url_for('delete', index=loop.index0) }}">Delete</a>
    </li>
{% endfor %}

Now your app adds todos dynamically without reloading the page! Pretty cool, right?

Of course, there’s still a lot we could do to improve this app. We could add user accounts, store todos in a database, add due dates or priority levels, or make it look prettier with some CSS. But this gives you a solid foundation to build on.

Remember, the key to building any app is to start simple and gradually add features. Don’t try to do everything at once - it’s easy to get overwhelmed that way. Just take it step by step, and before you know it, you’ll have a fully-functional app!

Testing is also crucial. As you add features, make sure to test each one thoroughly. Try to break your app - add empty todos, try to complete or delete non-existent todos, etc. It’s better to find and fix these issues yourself than to have your users discover them!

Building a to-do app might seem simple, but it teaches you a lot about full-stack development. You’re dealing with data storage, server-side logic, client-side rendering, and even a bit of async JavaScript. These are all skills that will serve you well in more complex projects.

So go ahead, build that to-do app. Customize it, break it, fix it, and most importantly, have fun with it. Happy coding!