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.
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.pyor an application factory.templates/andstatic/for HTML, CSS and JS.requirements.txtfrompip freeze.- Blueprints to split routes into modules.
- Configuration in environment variables, not in code.
Security basics
- Set a long random
SECRET_KEYfrom 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
- Add user accounts with Flask-Login.
- Use SQLAlchemy as an ORM.
- Write tests with pytest.
- Chart data with Python data visualization.
- Learn frontend with JavaScript basics.
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.