Quando un'app Python deve servire pagine web, gestire dati o offrire API, Flask permette di partire con poco codice senza rinunciare a una struttura seria. In questo flask tutorial mostro come creare il primo progetto, collegarlo a un database, organizzare il codice e prepararlo per test e produzione, con esempi adatti anche a chi sta costruendo un servizio cloud leggero.
Il percorso essenziale per costruire un'app Flask affidabile
- Ambiente isolato con virtual environment e installazione del framework.
- Routing, template e form per trasformare una funzione Python in una pagina utile.
- Application factory e blueprint per mantenere il progetto ordinato quando cresce.
- SQLite all'inizio, con passaggio a PostgreSQL quando aumentano dati e concorrenza.
- Test, sicurezza e deploy prima di considerare l'app pronta per utenti reali.

Installare Flask e avviare la prima applicazione
Flask è un microframework web per Python. “Micro” non significa limitato: il nucleo gestisce richieste, risposte, routing e template, mentre database, autenticazione e altri servizi vengono aggiunti quando servono. Secondo me questo approccio è il suo punto forte, perché evita di trascinare nel progetto componenti che non userai mai.
Prima di installare il framework, creo un ambiente virtuale. In questo modo le dipendenze del progetto restano separate da quelle del sistema e diventa molto più semplice replicare l'app su un server o in un container.
mkdir catalogo-flask
cd catalogo-flask
python -m venv .venv
source .venv/bin/activate
pip install FlaskSu Windows cambia soltanto il comando di attivazione dell'ambiente. Il principio resta identico: il terminale deve usare il Python contenuto in .venv, non quello globale.
Per iniziare basta un file chiamato app.py. Il decoratore @app.route() collega un percorso web a una funzione Python.
from flask import Flask
app = Flask(__name__)
@app.get("/")
def home():
return "Catalogo prodotti
"
if __name__ == "__main__":
app.run(debug=True)Avvio l'app con il comando Flask, indicando il modulo da caricare.
flask --app app run --debugLa modalità debug riavvia il server quando salvi una modifica e mostra errori dettagliati. È ottima durante lo sviluppo, ma va disattivata in produzione: il debugger interattivo può rivelare informazioni sensibili e non deve essere esposto a Internet.
Il primo errore da evitare
Molti principianti lavorano direttamente nella cartella Python globale e poi si ritrovano con versioni incompatibili di Flask o delle sue estensioni. Io considero il virtual environment una parte del progetto, non un passaggio opzionale. Aggiungo anche un file requirements.txt per rendere riproducibile l'installazione.
pip freeze > requirements.txtRouting, template e dati inviati dal browser
Una pagina reale raramente restituisce HTML scritto dentro una stringa Python. Flask usa Jinja, il suo motore di template, per separare la logica applicativa dalla presentazione. Questa divisione rende il codice più leggibile e permette di riutilizzare lo stesso layout in molte pagine.
Creo questa struttura minima:
catalogo-flask/
├── app.py
└── templates/
├── base.html
└── prodotti.htmlNel file base.html inserisco la struttura comune:
{% block title %}Catalogo{% endblock %}
{% block content %}{% endblock %}
La pagina dei prodotti eredita il layout e riceve i dati dalla view.
{% extends "base.html" %}
{% block title %}Prodotti{% endblock %}
{% block content %}
Prodotti disponibili
{% for prodotto in prodotti %}
- {{ prodotto.nome }} - {{ prodotto.prezzo }} euro
{% else %}
- Nessun prodotto disponibile
{% endfor %}
{% endblock %}La funzione Python usa render_template() per passare il contesto al template.
from flask import Flask, render_template
app = Flask(__name__)
@app.get("/prodotti")
def prodotti():
elenco = [
{"nome": "Notebook", "prezzo": 899},
{"nome": "Monitor", "prezzo": 249}
]
return render_template("prodotti.html", prodotti=elenco)Per i dati ricevuti dal browser, distinguo sempre tra GET e POST. Il primo legge una risorsa, il secondo invia dati e può modificare lo stato dell'applicazione.
from flask import request, render_template
@app.route("/cerca", methods=["GET", "POST"])
def cerca():
risultato = None
if request.method == "POST":
termine = request.form.get("termine", "").strip()
risultato = f"Hai cercato: {termine}"
return render_template("cerca.html", risultato=risultato)In un'applicazione concreta non inserirei mai direttamente valori dell'utente nelle query SQL. Uso sempre query parametrizzate o un ORM, perché concatenare stringhe può aprire la porta alla SQL injection. Anche la validazione lato server resta indispensabile, persino quando il form ha già controlli JavaScript.
Una struttura che regge quando il progetto cresce
Il file unico è perfetto per capire Flask, ma diventa scomodo appena compaiono autenticazione, database, API e pagine amministrative. Per progetti destinati a durare preferisco un'application factory, cioè una funzione che crea e configura l'applicazione quando serve.
catalogo-flask/
├── app/
│ ├── __init__.py
│ ├── db.py
│ ├── main.py
│ └── templates/
└── tests/Nel file app/__init__.py registro configurazione e blueprint.
from flask import Flask
def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY="dev",
DATABASE="catalogo.sqlite"
)
if test_config is not None:
app.config.update(test_config)
from .main import bp
app.register_blueprint(bp)
return appUn blueprint raggruppa route e funzioni correlate senza creare una seconda applicazione. Per esempio, il blueprint principale può contenere le pagine pubbliche, mentre admin gestisce il pannello riservato.
from flask import Blueprint, render_template
bp = Blueprint("main", __name__)
@bp.get("/")
def home():
return render_template("home.html")Per avviare questa versione uso:
flask --app "app:create_app()" run --debugLa factory richiede un po' di codice iniziale in più, ma offre vantaggi concreti. Posso creare un'app configurata per i test, un'altra per lo sviluppo e una terza per la produzione senza duplicare la logica. È una scelta che ripaga soprattutto quando il progetto deve essere mantenuto da più persone.
Collegare Flask a un database senza complicare tutto
Per un prototipo o un piccolo servizio, SQLite è spesso la scelta più razionale. È incluso in Python, non richiede un server separato e salva i dati in un file. Il limite emerge con molte scritture simultanee, quindi non lo considero automaticamente adatto a un'applicazione ad alto traffico.
| Database | Quando usarlo | Limite principale |
|---|---|---|
| SQLite | Prototipi, strumenti interni, piccoli siti | Scritture concorrenti limitate |
| PostgreSQL | Applicazioni web professionali e API | Richiede gestione di un server |
| MySQL | Progetti che già usano l'ecosistema MySQL | La scelta dipende da hosting e competenze disponibili |
Con SQLite posso partire dal modulo standard sqlite3. Apro una connessione per la richiesta e la chiudo quando il contesto dell'applicazione termina. L'oggetto g di Flask è utile per conservare la connessione durante la singola richiesta.
import sqlite3
from flask import current_app, g
def get_db():
if "db" not in g:
g.db = sqlite3.connect(
current_app.config["DATABASE"]
)
g.db.row_factory = sqlite3.Row
return g.db
def close_db(error=None):
db = g.pop("db", None)
if db is not None:
db.close()Per applicazioni con modelli più articolati preferisco spesso SQLAlchemy, un ORM che rappresenta tabelle e relazioni attraverso oggetti Python. Riduce il SQL ripetitivo e rende più semplice cambiare database, ma non elimina la necessità di capire indici, transazioni e relazioni.
Il criterio pratico è semplice. Se devo salvare poche entità e fare query lineari, sqlite3 è chiaro e sufficiente. Se prevedo utenti, ordini, ruoli, migrazioni e più ambienti, conviene introdurre un ORM e uno strumento per le migrazioni prima che lo schema diventi difficile da modificare.
Leggi anche: SQL COLLATE spiegato bene e senza errori
Inizializzare lo schema
Evito di creare le tabelle automaticamente all'avvio di ogni richiesta. Meglio usare un comando esplicito, così so esattamente quando lo schema cambia e posso replicare il processo nei diversi ambienti.
import click
from flask import current_app
from .db import get_db
@click.command("init-db")
def init_db_command():
db = get_db()
db.executescript("""
CREATE TABLE IF NOT EXISTS prodotto (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT NOT NULL,
prezzo REAL NOT NULL
);
""")
db.commit()
click.echo("Database inizializzato.")Il comando deve essere registrato nella factory con app.cli.add_command(). In produzione non basta copiare il file SQLite dal computer locale: bisogna inizializzare il database sul server e configurare correttamente il percorso dei dati.
Creare API JSON e testare ciò che funziona
Flask non serve soltanto pagine HTML. Con poche righe posso creare un endpoint JSON per una dashboard, un'app mobile o un servizio interno.
from flask import jsonify
@app.get("/api/prodotti")
def api_prodotti():
prodotti = [
{"id": 1, "nome": "Notebook", "prezzo": 899}
]
return jsonify(prodotti)Quando i modelli diventano complessi, trasformo esplicitamente i dati in tipi compatibili con JSON. Questo evita di restituire direttamente oggetti del database che il serializer non sa interpretare.
Il test client di Flask permette di simulare richieste senza avviare un server reale. È uno dei motivi per cui una factory ben progettata fa la differenza.
import pytest
from app import create_app
@pytest.fixture
def client():
app = create_app({
"TESTING": True,
"DATABASE": ":memory:"
})
with app.test_client() as client:
yield client
def test_home(client):
response = client.get("/")
assert response.status_code == 200Parto dai test delle route principali, degli errori e delle regole di accesso. Non cerco una copertura perfetta come obiettivo astratto: mi interessa verificare i punti che, se si rompono, danneggiano davvero utenti o dati.
Per le API controllo almeno status code, struttura JSON e validazione degli input. Un endpoint che risponde con 200 anche quando manca un campo obbligatorio rende difficile il lavoro del client e nasconde errori che andrebbero segnalati con 400 o 422.
Portare Flask in produzione senza sorprese
Il server integrato di Flask è pensato per lo sviluppo, non per sostenere traffico reale. In produzione uso un server WSGI come Gunicorn o Waitress, spesso dietro un reverse proxy che gestisce TLS, compressione e richieste statiche.
La configurazione merita la stessa attenzione del codice. La SECRET_KEY non deve essere la stringa di sviluppo presente negli esempi e non va salvata nel repository. La genero casualmente e la passo tramite variabile d'ambiente o secret manager.
import secrets
print(secrets.token_hex(32))Prima del deploy verifico anche questi punti:
- Debug disattivato e gestione degli errori senza stack trace pubblico.
- Segreti fuori dal codice, inclusi password e chiavi API.
- Cookie protetti con impostazioni Secure, HttpOnly e SameSite adeguate.
- CSRF per i form che modificano dati, soprattutto in presenza di sessioni.
- Backup del database e procedura verificata di ripristino.
- Logging di errori, tempi di risposta e accessi anomali.
Per un piccolo progetto cloud, containerizzare Flask può semplificare il rilascio, ma Docker non risolve automaticamente problemi di database, sicurezza o osservabilità. Prima definisco come vengono conservati i dati e come eseguo le migrazioni, poi scelgo l'infrastruttura.
Il passaggio da SQLite a PostgreSQL va pianificato quando l'app inizia a ricevere molte scritture, richiede query concorrenti o deve scalare su più istanze. Cambiare database in anticipo senza una necessità concreta aggiunge lavoro; aspettare troppo può invece trasformare una migrazione semplice in un intervento rischioso.
Il prossimo progetto dovrebbe avere un confine piccolo
Il modo più efficace per imparare Flask è costruire un'applicazione ridotta ma completa, per esempio un catalogo con tre tabelle, autenticazione di base, una pagina HTML e un endpoint JSON. In questo perimetro si incontrano già routing, template, database, validazione e test, cioè quasi tutti i concetti che poi ricompaiono nei progetti più grandi.
Io eviterei di iniziare da un social network o da un e-commerce completo. Un progetto da 5-10 route, con un database piccolo e almeno alcuni test automatici, insegna più di una demo enorme copiata senza capire il flusso tra richiesta, view, servizio e persistenza.
Quando quella base funziona, si possono aggiungere blueprint, migrazioni, autenticazione e deploy un elemento alla volta. È così che Flask dà il meglio: rimane leggibile mentre l'app cresce, ma lascia spazio per scegliere gli strumenti adatti alle esigenze reali.