Programming • Updated 8 November 2026

Flask for Beginners: Build a Python Web App With Routes, Templates, Forms and SQLite Step by Step

Learn Flask from scratch: installing, routes, Jinja templates, forms, GET and POST, SQLite storage, a small to-do app, project structure, security basics and how to deploy to a simple host.

FlaskPythonweb developmentJinjaSQLite
⏱ 5 min read

Flask is a lightweight Python web framework. It gives you routing, templates and request handling without forcing a rigid structure, which makes it ideal for learning how websites work and for building small apps and APIs. In this guide you will build a simple to-do app with a database.

Prerequisites

You should know basic Python. If not, read Python basics and, for the database, SQL basics.

Setup

mkdir todo && cd todo
python -m venv venv
source venv/bin/activate      # Windows: venv\Scripts\activate
pip install flask

A virtual environment keeps this project's packages separate from other projects.

Hello Flask

# app.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def home():
    return "Hello from Flask"

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

Run python app.py and open http://127.0.0.1:5000. The @app.route decorator maps a URL to a function. Debug mode reloads on code change, but never use it in production.

Templates with Jinja

Create a folder named templates and a file index.html:

<!doctype html>
<h1>My tasks</h1>
<form method="post" action="/add">
  <input name="title" required>
  <button>Add</button>
</form>
<ul>
{% for t in tasks %}
  <li>{{ t["title"] }} <a href="/done/{{ t['id'] }}">done</a></li>
{% endfor %}
</ul>

Jinja double braces print values, and {% %} blocks hold logic. Jinja escapes values by default, which protects against cross-site scripting.

SQLite database

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

app = Flask(__name__)

def db():
    conn = sqlite3.connect("todo.db")
    conn.row_factory = sqlite3.Row
    return conn

with db() as conn:
    conn.execute(
        "CREATE TABLE IF NOT EXISTS tasks "
        "(id INTEGER PRIMARY KEY, title TEXT, done INTEGER DEFAULT 0)"
    )

@app.route("/")
def index():
    with db() as conn:
        tasks = conn.execute("SELECT * FROM tasks WHERE done = 0").fetchall()
    return render_template("index.html", tasks=tasks)

@app.route("/add", methods=["POST"])
def add():
    title = request.form["title"].strip()
    if title:
        with db() as conn:
            conn.execute("INSERT INTO tasks (title) VALUES (?)", (title,))
    return redirect("/")

@app.route("/done/<int:task_id>")
def done(task_id):
    with db() as conn:
        conn.execute("UPDATE tasks SET done = 1 WHERE id = ?", (task_id,))
    return redirect("/")

Note the question marks in the queries. These parameters prevent SQL injection, so never build queries by pasting user input into strings. A real app should change state with POST requests rather than links, as links can be triggered by other sites.

Project structure for growth

  • app.py or an application factory.
  • templates/ and static/ for HTML, CSS and JS.
  • requirements.txt from pip freeze.
  • Blueprints to split routes into modules.
  • Configuration in environment variables, not in code.

Security basics

  • Set a long random SECRET_KEY from an environment variable for sessions.
  • Add CSRF protection with Flask-WTF for forms.
  • Hash passwords with a library like Werkzeug's generate_password_hash. Never store plain passwords.
  • Validate input on the server, and turn off debug mode in production.
  • Serve over HTTPS.

Returning JSON

from flask import jsonify

@app.route("/api/tasks")
def api_tasks():
    with db() as conn:
        rows = conn.execute("SELECT id, title FROM tasks").fetchall()
    return jsonify([dict(r) for r in rows])

Deployment

Do not use the development server in production. Use a WSGI server such as gunicorn behind a web server like Nginx, and choose a host that supports Python apps, such as a VPS or a platform-as-a-service. Shared cPanel hosting may support Python through its application manager, so check with your provider. Compare options in our WordPress vs custom site guide.

Next steps

Understanding the request cycle

When a visitor opens a URL, the browser sends an HTTP request. Flask matches the path to a route function, which can read form fields, query parameters and cookies, talk to the database and then return a response. That response might be HTML rendered from a template, JSON for a JavaScript front end, or a redirect. Seeing this cycle clearly makes debugging easier: if a page is blank, check whether the route was matched, whether the function returned something, and whether the template received the variables you expected. The Flask debugger shows a traceback in the browser, and the terminal prints each request with its status code.

Practice ideas

  • Add edit and delete for tasks, using POST forms and CSRF tokens.
  • Create a URL shortener with a table of codes and targets.
  • Build a small expense tracker linked to the ideas in the Python basics project.
  • Expose a JSON API and call it with the JavaScript fetch API.

Finish each practice project by writing a README, a requirements file and a few tests. These habits matter more to employers than the project itself.

Frequently asked questions

What is Flask?

A lightweight Python web framework for building websites and APIs.

Flask or Django?

Flask is simpler for learning and small apps, Django includes more built-in features for large projects.

How do I run a Flask app?

Run python app.py or flask run in the project folder.

Is debug mode safe in production?

No, always turn it off.

How do I prevent SQL injection?

Use parameterised queries or an ORM.

Where can I host a Flask app?

On a VPS, a platform-as-a-service or any host supporting Python and WSGI.

Last reviewed 8 November 2026. Rates, limits and rules change, so confirm figures with the official source before you act.