Flask TODO API Tutorial: Build a Python Backend for Tasks

Spread the love

Flask TODO API Tutorial: Build a Python Backend for Tasks

Hey there, future web developer! If you have wanted to build a real web application but had no idea where to start, you are in the right place. Today, we are diving deep into creating a functional, user-friendly TODO list app. We will use Python and Flask to power our very own Flask REST API Tutorial: Build a Python Web Service. By the end, you will have a working app and a clear understanding of its core components. This means you will master essential backend concepts with our Flask TODO API!

What We Are Building: Your First Flask TODO API App!

We’re going to build a super useful web application. It is a simple, yet powerful, TODO list. You will be able to add new tasks, mark them as complete, and even delete them. This project is perfect for understanding how a frontend (what you see) talks to a backend (where data lives). Plus, it is incredibly satisfying to build something tangible. We will create a robust Flask backend that acts as our Flask TODO API. It will manage all your tasks effortlessly.

This app will use basic HTML for structure, CSS for looks, and JavaScript to make things interactive. The real magic happens with Flask. It is a Python web framework that handles all our data. It makes the communication between your browser and our server seamless. This means your tasks will persist!

Setting Up Your Project

Before we jump into code, let’s get our environment ready. You will need Python installed on your machine. We will use a virtual environment. This keeps our project dependencies neat. First, create a new directory for your project. Then, open your terminal inside this directory. Now, run these commands:

Pro Tip: Virtual environments are your best friends in Python development! They prevent conflicts between different project dependencies. Always use one for new projects.

python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install Flask Flask-CORS

Flask is our web framework. Flask-CORS helps us handle cross-origin requests. This is important when our frontend serves from a different port. We’ll explore this more later. You are now ready to code!

HTML Structure: Crafting the Frontend Layout

Our HTML file will be straightforward. It provides the skeleton for our TODO list. We will have an input field for new tasks. There will also be a button to add them. A list will display all our tasks. Each task will have a checkbox and a delete button. This makes managing tasks simple. Here’s the basic structure we’ll use:

As you can see, we have a main container. It holds our header, input section, and the task list. The <ul id="todo-list"></ul> is where our tasks will dynamically appear. This setup keeps everything organized. Moreover, it makes styling much easier.

CSS Styling: Making it Pretty

A functional app is great, but a good-looking one is even better! Our CSS will give our TODO list a clean, modern feel. We’ll add some basic styling for the body, containers, and task items. This improves user experience significantly. Don’t worry about this part too much. Just copy and paste, then feel free to play around with colors and fonts later! Styling makes a huge difference. For more CSS tips, check out CSS-Tricks.

This CSS gives our app a fresh, minimal look. We use Flexbox for layout, which makes aligning items a breeze. The task items also get a nice hover effect. This indicates they are interactive. Remember, good design keeps users engaged!

JavaScript Magic: Interacting with Our Flask TODO API

JavaScript is where our frontend comes alive. It handles adding, completing, and deleting tasks. More importantly, it communicates with our Flask backend. This connection makes our app dynamic. We will use the Fetch API for all our HTTP requests. This lets us talk to our Flask TODO API. It sends and receives data seamlessly. Let me explain what’s happening here:

app.py

import json
from flask import Flask, request, jsonify

# Initialize the Flask application
app = Flask(__name__)

# In-memory database for our TODO items
# In a real application, you would use a proper database (SQLAlchemy, MongoDB, etc.)
todos = [
    {"id": 1, "title": "Learn Flask", "description": "Explore Flask framework documentation.", "completed": False},
    {"id": 2, "title": "Build a REST API", "description": "Design and implement API endpoints for TODOs.", "completed": False}
]
next_id = 3 # Simple counter for new TODOs

@app.route('/')
def home():
    """
    Root endpoint provides a simple message.
    """
    return jsonify({"message": "Welcome to the Flask TODO API! Use /todos to manage your tasks."})

@app.route('/todos', methods=['GET'])
def get_todos():
    """
    Retrieve all TODO items.
    Example: curl http://127.0.0.1:5000/todos
    """
    return jsonify(todos)

@app.route('/todos/<int:todo_id>', methods=['GET'])
def get_todo(todo_id):
    """
    Retrieve a single TODO item by its ID.
    Example: curl http://127.0.0.1:5000/todos/1
    """
    todo = next((t for t in todos if t['id'] == todo_id), None)
    if todo:
        return jsonify(todo)
    return jsonify({"message": "Todo not found"}), 404

@app.route('/todos', methods=['POST'])
def add_todo():
    """
    Add a new TODO item.
    Requires JSON payload with 'title' and optionally 'description', 'completed'.
    Example: curl -X POST -H "Content-Type: application/json" -d '{"title": "Buy groceries", "description": "Milk, eggs, bread"}' http://127.0.0.1:5000/todos
    """
    if not request.is_json:
        return jsonify({"message": "Request must be JSON"}), 400

    data = request.get_json()
    if 'title' not in data:
        return jsonify({"message": "Title is required"}), 400

    global next_id
    new_todo = {
        "id": next_id,
        "title": data['title'],
        "description": data.get('description', ''),
        "completed": data.get('completed', False)
    }
    todos.append(new_todo);
    next_id += 1
    return jsonify(new_todo), 201 # 201 Created status

@app.route('/todos/<int:todo_id>', methods=['PUT'])
def update_todo(todo_id):
    """
    Update an existing TODO item.
    Requires JSON payload with fields to update (title, description, completed).
    Example: curl -X PUT -H "Content-Type: application/json" -d '{"completed": true}' http://127.0.0.1:5000/todos/1
    """
    todo = next((t for t in todos if t['id'] == todo_id), None)
    if not todo:
        return jsonify({"message": "Todo not found"}), 404

    if not request.is_json:
        return jsonify({"message": "Request must be JSON"}), 400

    data = request.get_json()
    todo.update({
        "title": data.get('title', todo['title']),
        "description": data.get('description', todo['description']),
        "completed": data.get('completed', todo['completed'])
    })
    return jsonify(todo)

@app.route('/todos/<int:todo_id>', methods=['DELETE'])
def delete_todo(todo_id):
    """
    Delete a TODO item.
    Example: curl -X DELETE http://127.0.0.1:5000/todos/2
    """
    global todos
    original_len = len(todos)
    todos = [t for t in todos if t['id'] != todo_id]
    if len(todos) < original_len:
        return jsonify({"message": "Todo deleted successfully"}), 200
    return jsonify({"message": "Todo not found"}), 404

if __name__ == '__main__':
    # To run the app:
    # 1. Save this file as app.py
    # 2. Open your terminal/command prompt
    # 3. Make sure you have Flask installed: pip install Flask
    # 4. Run the app: python app.py
    # The app will be available at http://127.0.0.1:5000/
    app.run(debug=True) # debug=True enables reloader and debugger

This script connects our user interface to our backend. It listens for clicks and input events. Then, it sends requests to our Flask server. It updates the UI based on the server’s responses. This is the core of any interactive web app! We are effectively building a single-page application experience.

How Our Flask TODO API Works Together: The Backend Logic

Now for the heart of our application: the Flask backend. This is where our Flask TODO API truly shines. It will manage our tasks. We will use a simple in-memory list for now. This keeps things easy to understand. Later, you can connect it to a database! This section brings together Python, HTTP requests, and web development.

Initializing Flask and Routes

First, we initialize our Flask app. We will also set up CORS. This allows our frontend (running locally on one port) to talk to our backend (on another port). It’s a common setup for development. We define routes. These are specific URLs that our app responds to. Each route maps to a Python function. This function performs an action. It handles creating, reading, updating, or deleting tasks.

from flask import Flask, request, jsonify
from flask_cors import CORS

app = Flask(__name__)
CORS(app) # Enable CORS for all routes

todos = [] # In-memory storage for our TODOs
next_id = 1

@app.route('/todos', methods=['GET'])
def get_todos():
    return jsonify(todos)

Here, the /todos GET route returns all current tasks. jsonify converts our Python list to JSON. This is important for web APIs. It’s how data is commonly exchanged over the web. We also track next_id for unique task identifiers.

Handling Our TODOs (CREATE, READ, UPDATE, DELETE)

Our Flask application needs to handle all CRUD operations. CRUD stands for Create, Read, Update, and Delete. These are fundamental for any data-driven application. We will create routes for each of these actions. Our JavaScript frontend will send requests to these specific routes. This is how we interact with our tasks.

# ... (previous code)

@app.route('/todos', methods=['POST'])
def add_todo():
    global next_id
    new_todo = request.json
    new_todo['id'] = next_id
    new_todo['completed'] = False
    todos.append(new_todo)
    next_id += 1
    return jsonify(new_todo), 201

@app.route('/todos/<int:todo_id>', methods=['PUT'])
def update_todo(todo_id):
    todo_data = request.json
    for todo in todos:
        if todo['id'] == todo_id:
            todo['title'] = todo_data.get('title', todo['title'])
            todo['completed'] = todo_data.get('completed', todo['completed'])
            return jsonify(todo)
    return jsonify({'message': 'Todo not found'}), 404

@app.route('/todos/<int:todo_id>', methods=['DELETE'])
def delete_todo(todo_id):
    global todos
    todos = [todo for todo in todos if todo['id'] != todo_id]
    return jsonify({'message': 'Todo deleted'}), 204

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

The POST route adds a new task. The PUT route modifies an existing task. Finally, the DELETE route removes a task. Each route returns a JSON response. It also includes an appropriate HTTP status code. For example, 201 Created or 404 Not Found. This makes our API robust. For more on handling HTTP requests in Python, check out our guide on the Python Requests Library: Simplified HTTP & API Guide.

Connecting Frontend to Backend

Our JavaScript code (the frontend) sends requests to these Flask API endpoints. When you click “Add Task,” JavaScript makes a POST request to /todos. When you check a box, it’s a PUT request to update the task’s status. Deleting a task is a DELETE request. The frontend then updates itself with the new data from the Flask API. This seamless communication creates a truly interactive experience. It’s exciting to see both parts working in harmony!

Keep Learning: The beauty of web development is how different technologies work together. Master the basics, and the possibilities are endless!

Tips to Customise Your TODO App

You’ve built an amazing app! Here are some ideas to make it even better:

  1. Add Persistence: Right now, your tasks disappear when the server restarts. Integrate a database like SQLite or PostgreSQL using Flask-SQLAlchemy. This will store your data permanently.
  2. User Authentication: Implement user login/registration. Each user can then have their own private TODO list.
  3. Advanced Filtering: Add options to filter tasks by completion status, due date, or categories. This makes your app more powerful.
  4. Better UI/UX: Experiment with different CSS frameworks like Tailwind CSS or Bootstrap. Make your app look even more professional.

Conclusion: You’ve Built a Real Flask TODO API!

Wow, give yourself a pat on the back! You just built a fully functional TODO list web application. You’ve created a frontend with HTML, CSS, and JavaScript. More importantly, you mastered the backend using Python and Flask. This powerful combination forms a complete Flask TODO API. You understand how client-side and server-side code interact. This is a huge milestone in your web development journey! Now, go forth and build more amazing things. Share your creation with the world. Feel proud of your new skills! What will you build next?

app.py

import json
from flask import Flask, request, jsonify

# Initialize the Flask application
app = Flask(__name__)

# In-memory database for our TODO items
# In a real application, you would use a proper database (SQLAlchemy, MongoDB, etc.)
todos = [
    {"id": 1, "title": "Learn Flask", "description": "Explore Flask framework documentation.", "completed": False},
    {"id": 2, "title": "Build a REST API", "description": "Design and implement API endpoints for TODOs.", "completed": False}
]
next_id = 3 # Simple counter for new TODOs

@app.route('/')
def home():
    """
    Root endpoint provides a simple message.
    """
    return jsonify({"message": "Welcome to the Flask TODO API! Use /todos to manage your tasks."})

@app.route('/todos', methods=['GET'])
def get_todos():
    """
    Retrieve all TODO items.
    Example: curl http://127.0.0.1:5000/todos
    """
    return jsonify(todos)

@app.route('/todos/<int:todo_id>', methods=['GET'])
def get_todo(todo_id):
    """
    Retrieve a single TODO item by its ID.
    Example: curl http://127.0.0.1:5000/todos/1
    """
    todo = next((t for t in todos if t['id'] == todo_id), None)
    if todo:
        return jsonify(todo)
    return jsonify({"message": "Todo not found"}), 404

@app.route('/todos', methods=['POST'])
def add_todo():
    """
    Add a new TODO item.
    Requires JSON payload with 'title' and optionally 'description', 'completed'.
    Example: curl -X POST -H "Content-Type: application/json" -d '{"title": "Buy groceries", "description": "Milk, eggs, bread"}' http://127.0.0.1:5000/todos
    """
    if not request.is_json:
        return jsonify({"message": "Request must be JSON"}), 400

    data = request.get_json()
    if 'title' not in data:
        return jsonify({"message": "Title is required"}), 400

    global next_id
    new_todo = {
        "id": next_id,
        "title": data['title'],
        "description": data.get('description', ''),
        "completed": data.get('completed', False)
    }
    todos.append(new_todo);
    next_id += 1
    return jsonify(new_todo), 201 # 201 Created status

@app.route('/todos/<int:todo_id>', methods=['PUT'])
def update_todo(todo_id):
    """
    Update an existing TODO item.
    Requires JSON payload with fields to update (title, description, completed).
    Example: curl -X PUT -H "Content-Type: application/json" -d '{"completed": true}' http://127.0.0.1:5000/todos/1
    """
    todo = next((t for t in todos if t['id'] == todo_id), None)
    if not todo:
        return jsonify({"message": "Todo not found"}), 404

    if not request.is_json:
        return jsonify({"message": "Request must be JSON"}), 400

    data = request.get_json()
    todo.update({
        "title": data.get('title', todo['title']),
        "description": data.get('description', todo['description']),
        "completed": data.get('completed', todo['completed'])
    })
    return jsonify(todo)

@app.route('/todos/<int:todo_id>', methods=['DELETE'])
def delete_todo(todo_id):
    """
    Delete a TODO item.
    Example: curl -X DELETE http://127.0.0.1:5000/todos/2
    """
    global todos
    original_len = len(todos)
    todos = [t for t in todos if t['id'] != todo_id]
    if len(todos) < original_len:
        return jsonify({"message": "Todo deleted successfully"}), 200
    return jsonify({"message": "Todo not found"}), 404

if __name__ == '__main__':
    # To run the app:
    # 1. Save this file as app.py
    # 2. Open your terminal/command prompt
    # 3. Make sure you have Flask installed: pip install Flask
    # 4. Run the app: python app.py
    # The app will be available at http://127.0.0.1:5000/
    app.run(debug=True) # debug=True enables reloader and debugger

Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *