Django è un framework web open source scritto in Python che mette a disposizione una struttura già organizzata per costruire applicazioni server-side. Routing, accesso al database, autenticazione, sessioni, form, template e pannello amministrativo fanno parte dello stesso ecosistema: invece di scegliere e collegare da zero molti componenti separati, parti da convenzioni e strumenti che lavorano già insieme.

Questo è anche il motivo per cui richiede più struttura iniziale rispetto a un framework leggero come Flask. Non è necessariamente un limite: per applicazioni che devono gestire utenti, dati, permessi e molte funzionalità correlate, avere alcune decisioni già prese può ridurre parecchio la complessità del progetto.

In questa guida vedremo prima come funziona il framework, poi lo installeremo e costruiremo una piccola applicazione completa con URL, view, template, model, migrazioni e Django Admin. Infine capiremo cosa cambia quando il progetto deve andare in produzione e quando conviene scegliere questa soluzione invece di Flask o FastAPI.

Se invece stai ancora imparando variabili, funzioni, moduli, errori, pip e ambienti virtuali, conviene partire dalla guida su come programmare in Python. Un framework diventa molto più semplice quando riesci a distinguere ciò che appartiene al linguaggio da ciò che aggiunge lo strumento che stai utilizzando.

Cos’è Django e a cosa serve

Django è un framework per lo sviluppo di applicazioni web con Python. Lavora principalmente nel backend: riceve richieste HTTP, decide quale codice deve gestirle, può leggere o modificare dati, applicare regole di autorizzazione e infine costruire una risposta da restituire al client.

Il flusso più semplice può essere rappresentato così:

browser
   ↓
richiesta HTTP
   ↓
URLconf
   ↓
view
   ↓
logica applicativa / model / database
   ↓
template oppure altra risposta
   ↓
risposta HTTP
   ↓
browser
Flusso di una richiesta dal browser a URLconf, view, model, database e risposta
Una richiesta viene associata a una view attraverso l’URLconf; la view può utilizzare model e database prima di produrre la risposta.

Non si tratta quindi di un linguaggio di programmazione. Il linguaggio è Python; il framework fornisce invece una serie di classi, funzioni, convenzioni e componenti che organizzano problemi tipici dello sviluppo web.

La differenza è importante. Una condizione if, una funzione, una classe o un’importazione appartengono a Python. Model, QuerySet, urlpatterns, render() o il sistema delle migrazioni appartengono invece all’ecosistema del framework.

È un framework Python, non un linguaggio

Quando scrivi:

def saluta(nome):
    return f"Ciao {nome}"

stai usando soltanto Python.

Quando invece scrivi:

from django.http import HttpResponse

def home(request):
    return HttpResponse("Ciao da Django")

la funzione resta codice Python, ma HttpResponse e l’oggetto request fanno parte del framework.

Questo confine aiuta anche nel debugging. Se ricevi un SyntaxError, il problema appartiene probabilmente al codice Python. Se l’applicazione non trova una route o segnala una configurazione mancante in INSTALLED_APPS, stai lavorando invece con il livello introdotto dal framework.

Cosa significa davvero “batteries included”

Django viene spesso descritto come un framework batteries included. La formula è utile, ma rischia di diventare uno slogan se non specifichiamo cosa comporta.

Un nuovo progetto può utilizzare direttamente componenti per:

  • routing delle URL;
  • ORM e accesso al database;
  • migrazioni dello schema;
  • autenticazione e autorizzazioni;
  • utenti, gruppi e permessi;
  • sessioni;
  • form e validazione;
  • template HTML;
  • gestione di file statici;
  • protezioni per diversi problemi di sicurezza comuni;
  • pannello amministrativo;
  • middleware;
  • caching;
  • testing;
  • internazionalizzazione.

Non significa che il framework risolva automaticamente qualsiasi requisito. Significa che molti problemi ricorrenti hanno già un’implementazione coerente con il resto dello stack.

Se devi realizzare un gestionale, un portale editoriale, un’applicazione con account utente o un prodotto con molti modelli collegati, questo può evitare di assemblare e mantenere una lunga serie di librerie indipendenti.

Quando un framework integrato riduce la complessità

Immagina un’applicazione che deve gestire clienti, ordini, prodotti, utenti dello staff e differenti livelli di autorizzazione.

Il problema non è soltanto creare cinque URL.

Devi decidere anche:

come rappresento i dati?
come li salvo?
come modifico lo schema?
come autentico un utente?
come verifico i permessi?
come valido un form?
come gestisco la sessione?
come creo un'interfaccia per lo staff?

Con un framework minimale puoi risolvere ciascuna domanda scegliendo componenti differenti. Qui molte di queste decisioni arrivano con una risposta già integrata.

Il vantaggio reale non è quindi “il framework scrive tutto al posto tuo”. È che riduce il numero di decisioni infrastrutturali che devi prendere prima di lavorare sul dominio specifico dell’applicazione.

Come passa una richiesta HTTP attraverso il framework

Per capire il meccanismo conviene seguire una richiesta concreta.

Supponiamo che il browser richieda:

/articoli/

L’applicazione non cerca automaticamente un file chiamato articoli.html. La richiesta attraversa una catena di componenti che determinano quale codice deve essere eseguito.

URLconf: come viene scelta la view

Le URL vengono associate a view attraverso il sistema di routing.

Un file urls.py può contenere, per esempio:

from django.urls import path
from . import views

urlpatterns = [
    path("articoli/", views.lista_articoli, name="lista_articoli"),
]

Quando arriva una richiesta compatibile con articoli/, viene chiamata lista_articoli.

Il routing separa quindi l’indirizzo pubblico dal codice che realizza il comportamento.

Puoi modificare l’URL senza dover rinominare la funzione, oppure riutilizzare una view all’interno di una struttura di URL più articolata.

View: dove viene eseguita la logica

Una view riceve la richiesta e restituisce una risposta.

Nella forma più semplice:

from django.http import HttpResponse

def lista_articoli(request):
    return HttpResponse("Elenco articoli")

Nelle applicazioni reali la view può invece interrogare il database, verificare permessi, elaborare input e passare dati a un template.

Per esempio:

from django.shortcuts import render
from .models import Articolo

def lista_articoli(request):
    articoli = Articolo.objects.all()
    return render(
        request,
        "blog/lista.html",
        {"articoli": articoli},
    )

Qui la view coordina due componenti differenti: recupera dati attraverso il model e li consegna al sistema dei template.

Model e ORM: come vengono gestiti i dati

Un model descrive dati e relazioni utilizzando classi Python.

Per esempio:

from django.db import models

class Articolo(models.Model):
    titolo = models.CharField(max_length=200)
    pubblicato = models.BooleanField(default=False)

L’ORM usa queste definizioni per costruire query attraverso un livello a oggetti.

Puoi quindi scrivere:

Articolo.objects.filter(pubblicato=True)

invece di costruire manualmente una query SQL per ogni operazione.

Questo non significa che SQL smetta di esistere. Il database continua a lavorare con query e tabelle: l’ORM introduce uno strato Python che permette di esprimere molte operazioni in modo coerente con il resto dell’applicazione.

Per progetti reali rimane quindi utile capire database relazionali, indici, transazioni e comportamento delle query. L’astrazione semplifica molte operazioni, ma non elimina il funzionamento sottostante.

Template: come viene generato l’HTML

I template separano la presentazione HTML dalla maggior parte della logica applicativa.

Un template potrebbe contenere:

<h1>Articoli</h1>

<ul>
    {% for articolo in articoli %}
        <li>{{ articolo.titolo }}</li>
    {% endfor %}
</ul>

La view passa la variabile articoli, mentre il template decide come presentarla.

Questo permette di mantenere il codice Python lontano da grandi blocchi HTML e rende più comprensibile il confine tra logica e presentazione.

MVT e MVC: somiglianze e differenze senza forzare l’analogia

Per descrivere Django viene spesso usata la sigla MVT, Model-View-Template.

Una semplificazione utile è:

Model → dati e logica collegata ai dati
View → gestione della richiesta e coordinamento
Template → presentazione

Se conosci il pattern MVC, potresti notare che i nomi non coincidono perfettamente. La view del framework svolge parte del ruolo che in altre architetture viene associato a un controller, mentre il template copre gran parte della presentazione.

Conviene però evitare di cercare una corrispondenza uno-a-uno obbligatoria. La cosa importante è capire le responsabilità reali dei componenti, non forzare questa architettura dentro una terminologia nata in altri contesti.

Cosa trovi già integrato nel framework

Il valore di Django emerge soprattutto quando un progetto supera la prima pagina statica.

Routing e rendering sono solo l’inizio.

ORM e migrazioni

L’ORM permette di definire modelli, costruire query e rappresentare relazioni tra oggetti.

Quando la struttura dei model cambia, il sistema delle migrazioni descrive come deve evolvere lo schema del database.

Il flusso tipico è:

modifico models.py
        ↓
makemigrations
        ↓
viene creata una migrazione
        ↓
migrate
        ↓
lo schema del database viene aggiornato

Il file di migrazione diventa parte della cronologia del progetto e può essere mantenuto sotto controllo versione insieme al codice.

Autenticazione, utenti, permessi e sessioni

Il framework include un sistema di autenticazione che gestisce utenti, password, gruppi e permessi.

Non devi implementare da zero hashing delle password, sessioni o controlli di base sull’accesso alle view.

Questo non impedisce personalizzazioni. Un progetto può utilizzare un modello utente personalizzato, backend di autenticazione differenti o regole di autorizzazione specifiche.

Il punto è diverso: non stai partendo da una pagina bianca per una funzionalità delicata come l’autenticazione.

Form e validazione

Il sistema dei form aiuta a:

  • definire campi;
  • convertire valori ricevuti;
  • eseguire validazioni;
  • mostrare errori;
  • collegare form e model attraverso ModelForm;
  • gestire correttamente diversi aspetti delle richieste POST.

È particolarmente utile quando un’applicazione contiene molte operazioni CRUD e form server-side.

Se invece il frontend è completamente separato e comunica soltanto attraverso API, parte di questo livello può diventare meno centrale.

Django Admin

Uno dei componenti più riconoscibili è Django Admin.

Partendo dai model puoi costruire rapidamente un’interfaccia per visualizzare, creare, modificare e cancellare dati.

Per esempio:

from django.contrib import admin
from .models import Articolo

admin.site.register(Articolo)

Dopo aver registrato il model e creato un utente amministratore, puoi gestirne gli oggetti dall’interfaccia /admin/.

Questo è molto utile per back office, redazioni, prototipi, amministrazione interna e gestione operativa.

Django Admin non va però confuso con il frontend pubblico dell’applicazione. È uno strumento amministrativo configurabile, non un sostituto universale dell’interfaccia che devi progettare per gli utenti finali.

Protezioni di sicurezza integrate e loro limiti

Sono disponibili meccanismi per diversi problemi comuni della sicurezza web. I template applicano escaping in molti contesti, il middleware può proteggere dalle richieste CSRF, le normali query costruite dall’ORM usano parametrizzazione e sono disponibili protezioni contro il clickjacking.

La documentazione sulla sicurezza di Django chiarisce però un principio importante: la sicurezza rimane multilivello.

Puoi comunque introdurre vulnerabilità utilizzando HTML non fidato in modo scorretto, disattivando protezioni, costruendo raw SQL pericoloso, configurando male HTTPS, accettando upload senza limiti o lasciando segreti nel repository.

Dire che il framework dispone di funzioni di sicurezza integrate è corretto.

Dire che un’applicazione sia automaticamente sicura non lo è.

Installazione e ambiente di lavoro

Per iniziare serve prima una versione di Python compatibile.

Se non hai ancora familiarità con interprete, pip, moduli e ambienti virtuali, fermarti un momento sulle basi del linguaggio ti farà risparmiare parecchia confusione più avanti.

Prerequisiti: Python e ambiente virtuale

Verifica innanzitutto Python:

python --version

Su alcuni sistemi Unix-like il comando disponibile può essere:

python3 --version

Per controllare la compatibilità aggiornata è preferibile utilizzare la documentazione ufficiale, perché le versioni supportate cambiano nel tempo.

Crea quindi una cartella di lavoro:

mkdir django-demo
cd django-demo

e un ambiente virtuale:

python -m venv .venv

Su macOS o Linux puoi normalmente attivarlo con:

source .venv/bin/activate

Su Windows PowerShell:

.venv\Scripts\Activate.ps1

L’ambiente virtuale isola le dipendenze del progetto da quelle installate globalmente nel sistema.

Quale versione scegliere

Al momento della verifica di questa guida, Django 6.1.1 è la release ufficiale più recente e il ramo 6.1 supporta Python 3.12, 3.13 e 3.14.

Django 5.2 è invece una release LTS ancora coperta dal supporto esteso.

Questo non significa che un nuovo progetto debba sempre scegliere automaticamente l’LTS. Il criterio migliore è verificare:

  • versione Python disponibile;
  • compatibilità delle dipendenze;
  • policy di manutenzione del progetto;
  • durata prevista dell’applicazione;
  • disponibilità del team ad aggiornare il framework.

Per un progetto nuovo senza vincoli particolari, partire dall’ultima release stabile e dall’ultima patch disponibile è generalmente la scelta più lineare. Per software con cicli di validazione lunghi o dipendenze specifiche, una release LTS può essere più conveniente.

La pagina ufficiale dei download mantiene aggiornati release e periodi di supporto.

Installare il pacchetto con pip

Con l’ambiente virtuale attivo:

python -m pip install Django

Poi verifica l’installazione:

python -m django --version

Dovresti ricevere il numero della versione installata.

Utilizzare python -m pip invece del semplice pip rende inoltre più esplicito quale interprete Python sta eseguendo il package manager, cosa utile soprattutto quando sul computer sono presenti più installazioni.

Creare il primo progetto Django

Ora possiamo creare la struttura principale dell’applicazione.

Esegui:

django-admin startproject config .

Il punto finale indica di creare il progetto nella directory corrente.

Otterrai una struttura simile:

django-demo/
├── manage.py
└── config/
    ├── __init__.py
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

Cosa contengono manage.py, settings.py e urls.py

manage.py è l’utility da riga di comando associata al progetto. La utilizzeremo per avviare il server di sviluppo, creare app, eseguire migrazioni e molte altre operazioni.

settings.py contiene la configurazione: app installate, database, middleware, template, internazionalizzazione, file statici e altri parametri.

urls.py contiene invece il routing principale del progetto.

Una delle competenze più importanti quando inizi è sapere in quale livello stai lavorando. Modificare una view quando il problema è in urls.py, oppure modificare un model quando manca semplicemente un’app in INSTALLED_APPS, porta facilmente a debugging inutile.

WSGI e ASGI: perché compaiono già nel progetto

La struttura genera anche:

wsgi.py
asgi.py

Sono due entry point attraverso i quali un application server può comunicare con il progetto.

WSGI rappresenta l’interfaccia storicamente utilizzata dalle applicazioni Python sincrone.

ASGI estende il modello verso scenari asincroni e connessioni di durata maggiore.

Il framework supporta view asincrone e, eseguito attraverso ASGI, può utilizzare uno stack di richieste completamente async quando i componenti coinvolti sono compatibili. Molte parti dispongono oggi di API asincrone, ma questo non significa che convertire una normale applicazione in async def migliori automaticamente le prestazioni.

La scelta deve dipendere dal tipo di workload.

Avviare il development server

Prima applichiamo le migrazioni già previste dalle app integrate:

python manage.py migrate

Ora avviamo il server:

python manage.py runserver

Aprendo:

http://127.0.0.1:8000/

dovresti vedere la pagina iniziale.

A questo punto il percorso è:

browser → runserver → progetto → risposta

Perché runserver non è un server di produzione

runserver è pensato per lo sviluppo locale.

È comodo perché avvia rapidamente l’applicazione e ricarica il codice durante le modifiche, ma non è progettato né testato come server di produzione.

Quando il progetto viene pubblicato servirà un application server WSGI o ASGI appropriato e una configurazione di deployment reale.

È un confine importante da imparare subito: vedere l’applicazione funzionare su localhost:8000 significa che il codice può essere eseguito, non che l’infrastruttura sia pronta per Internet.

Project e app: la differenza da capire subito

La terminologia può creare confusione soprattutto nelle prime ore.

Un project rappresenta la configurazione complessiva di una specifica applicazione o sito.

Una app è invece un componente Python che svolge una funzione definita all’interno del progetto.

Un project può contenere molte app.

Che cos’è un project

Il project contiene configurazione generale e routing principale.

Nel nostro esempio:

config/

rappresenta il package del progetto.

Qui vivono file come:

settings.py
urls.py
asgi.py
wsgi.py

Non è il posto in cui devi necessariamente mettere tutta la logica dell’applicazione.

Che cos’è una app

Creiamo ora una app:

python manage.py startapp catalogo

La nuova directory conterrà qualcosa di simile:

catalogo/
├── migrations/
│   └── __init__.py
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── tests.py
└── views.py

Aggiungila a INSTALLED_APPS dentro config/settings.py:

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "catalogo",
]

Perché una app non equivale automaticamente a una pagina

Un errore frequente consiste nel pensare:

1 app = 1 pagina

Non funziona così.

Una app può gestire decine di URL, model e template. Potresti avere una app catalogo con lista prodotti, scheda prodotto, ricerca, categorie e operazioni riservate.

Allo stesso modo, non è necessario creare una nuova app ogni volta che aggiungi una route.

La domanda corretta è più vicina a:

questa funzionalità rappresenta un dominio sufficientemente coerente da meritare un componente applicativo separato?

Creare la prima pagina

Facciamo ora funzionare realmente la nostra app.

Il risultato finale sarà:

browser
  ↓
config/urls.py
  ↓
catalogo/urls.py
  ↓
catalogo/views.py
  ↓
template HTML
  ↓
browser

Scrivere una view

Apri catalogo/views.py:

from django.shortcuts import render


def home(request):
    context = {
        "titolo": "Il mio catalogo Django",
    }

    return render(request, "catalogo/home.html", context)

La view riceve request, prepara un dizionario di dati e chiama render().

Creare le URL dell’app

Crea il file:

catalogo/urls.py

con questo contenuto:

from django.urls import path
from . import views


urlpatterns = [
    path("", views.home, name="home"),
]

La stringa vuota significa che home gestirà la root assegnata all’app.

Collegare le URL dell’app al progetto

Ora modifica config/urls.py:

from django.contrib import admin
from django.urls import include, path


urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("catalogo.urls")),
]

La funzione include() delega parte del routing al file dell’app.

Questo è un pattern importante: il progetto decide quale prefisso assegnare a un’app, mentre l’app può gestire internamente le proprie route.

Creare il template

Crea queste directory:

catalogo/
└── templates/
    └── catalogo/
        └── home.html

Dentro home.html inserisci:

<!doctype html>
<html lang="it">
<head>
    <meta charset="utf-8">
    <title>{{ titolo }}</title>
</head>
<body>
    <h1>{{ titolo }}</h1>
    <p>La prima pagina del progetto funziona.</p>
</body>
</html>

Riavvia runserver se necessario e visita:

http://127.0.0.1:8000/

Ora la risposta non è più la pagina iniziale predefinita: passa dalla nostra URL, entra nella view e viene costruita dal template.

Seguire la richiesta dal browser fino alla risposta

Questo piccolo esempio è più importante di quanto sembri.

Quando apri /, l’applicazione:

  1. riceve la richiesta;
  2. consulta il routing principale;
  3. entra nelle URL di catalogo;
  4. trova home;
  5. esegue la view;
  6. carica il template;
  7. sostituisce {{ titolo }} con il valore ricevuto;
  8. costruisce la risposta HTTP;
  9. la restituisce al browser.

Capire questa sequenza è più utile che memorizzare una cartella di codice.

Quando qualcosa non funziona puoi infatti chiederti in quale punto si interrompe il percorso.

Model, migrazioni e database: aggiungere dati all’app

La pagina attuale contiene soltanto dati definiti direttamente nella view.

Aggiungiamo ora informazioni persistenti.

Creare un primo model

Apri catalogo/models.py:

from django.db import models


class Articolo(models.Model):
    titolo = models.CharField(max_length=200)
    pubblicato_il = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.titolo

Il model Articolo descrive due campi.

CharField rappresenta una stringa con lunghezza massima.

DateTimeField(auto_now_add=True) salva automaticamente data e ora quando viene creato l’oggetto.

makemigrations e migrate: cosa fanno davvero

Dopo aver creato o modificato un model esegui:

python manage.py makemigrations

Il framework confronta lo stato dei model con le migrazioni conosciute e genera le operazioni necessarie.

Poi:

python manage.py migrate

applica quelle operazioni al database.

È utile distinguere nettamente i due passaggi:

makemigrations
→ descrive la modifica dello schema

migrate
→ applica le migrazioni al database

Non trattare le migrazioni come file temporanei da cancellare appena qualcosa non funziona. In un progetto mantenuto nel tempo rappresentano la cronologia delle modifiche dello schema.

SQLite per iniziare e PostgreSQL per un progetto reale

Un nuovo progetto utilizza SQLite per impostazione predefinita.

È una scelta molto comoda per imparare e per piccoli esperimenti perché non richiede l’installazione di un database server separato.

Quando però stai costruendo un’applicazione sostanziale destinata alla produzione, la scelta del database deve essere affrontata prima.

La documentazione ufficiale raccomanda PostgreSQL per gli ambienti di produzione e suggerisce, per progetti importanti, di sviluppare possibilmente con lo stesso database previsto per il deploy.

Se vuoi capire meglio caratteristiche, transazioni, concorrenza e casi d’uso, abbiamo dedicato una guida specifica a PostgreSQL.

Il criterio non dovrebbe essere:

in locale SQLite funziona, quindi userò sicuramente SQLite anche in produzione.

Dovrebbe essere:

quali requisiti di persistenza, concorrenza, operatività e infrastruttura avrà questa applicazione?

Registrare il model nel Django Admin

Apri catalogo/admin.py:

from django.contrib import admin
from .models import Articolo


admin.site.register(Articolo)

Ora crea un superuser:

python manage.py createsuperuser

Segui le richieste del terminale e poi apri:

http://127.0.0.1:8000/admin/

Dopo il login troverai il model Articolo nel pannello.

Puoi creare record senza aver costruito manualmente un’interfaccia CRUD dedicata.

Django Admin: cosa risolve e quali sono i suoi limiti

La rapidità con cui compare un’interfaccia amministrativa è uno dei motivi per cui il framework può essere molto produttivo nei progetti data-driven.

Non va però interpretata come assenza di lavoro sul prodotto.

Gestire i dati dal pannello amministrativo

Per un model registrato puoi ottenere rapidamente operazioni di:

lista
creazione
modifica
cancellazione
ricerca e filtri, quando configurati

Puoi poi personalizzare il comportamento attraverso classi ModelAdmin.

Per esempio:

from django.contrib import admin
from .models import Articolo


@admin.register(Articolo)
class ArticoloAdmin(admin.ModelAdmin):
    list_display = ("titolo", "pubblicato_il")
    search_fields = ("titolo",)

Con poche righe l’interfaccia diventa già più utile per lo staff.

Perché l’admin non sostituisce l’interfaccia pubblica

Il pannello è pensato soprattutto come strumento di gestione.

Se stai costruendo un ecommerce, una community o un SaaS, gli utenti finali avranno requisiti UX, percorsi, permessi e interazioni diversi da quelli dello staff amministrativo.

Usarlo come scorciatoia per un back office interno può essere ottimo.

Usarlo come unica interfaccia di qualsiasi prodotto solo perché esiste già è invece una decisione architetturale che va giustificata.

API e backend moderni

Il framework non obbliga a restituire pagine HTML.

Una view può restituire anche JSON.

Per esempio:

from django.http import JsonResponse


def stato(request):
    return JsonResponse({
        "status": "ok",
        "framework": "django",
    })

Questa è già una risposta utilizzabile da un altro software.

Per comprendere bene endpoint, metodi HTTP, richieste, risposte e contratti tra servizi conviene però separare il concetto generale di API dallo strumento specifico usato per crearle.

Quando entra in gioco Django REST Framework

Per API più strutturate, uno degli strumenti più diffusi nell’ecosistema è Django REST Framework.

È un progetto separato dal core e aggiunge componenti dedicati come:

  • serializer;
  • API view;
  • viewset;
  • router;
  • autenticazione;
  • permessi;
  • throttling;
  • pagination;
  • content negotiation.

Questo significa che puoi utilizzare Django come base per un backend API senza che il core debba occuparsi direttamente di ogni astrazione tipica di una REST API.

Quando un progetto API-first può favorire FastAPI

Se l’applicazione nasce principalmente come API, soprattutto quando type hint, validazione degli schemi, OpenAPI automatico e modello ASGI sono centrali fin dall’inizio, FastAPI può richiedere meno adattamento concettuale.

La soluzione integrata diventa particolarmente interessante quando l’API è invece una parte di un dominio più ampio che beneficia anche di ORM, autenticazione, admin e convenzioni coordinate.

Non esiste quindi una regola “API = FastAPI” o “progetto grande = Django”.

Bisogna osservare quale parte della complessità vuoi che il framework gestisca.

Dallo sviluppo alla produzione: cosa cambia davvero

La differenza tra tutorial e applicazione reale non è semplicemente mettere il progetto su un server.

In produzione cambiano sicurezza, configurazione, database, file, logging, deployment e gestione dei segreti.

La deployment checklist ufficiale è un riferimento importante prima di pubblicare un progetto.

Development server e application server

La prima regola è semplice:

python manage.py runserver

non è la strategia di produzione.

L’applicazione deve essere eseguita tramite un application server adatto a WSGI o ASGI.

La scelta precisa dipende dall’architettura e dall’ambiente di deployment, ma il principio rimane:

sviluppo locale ≠ infrastruttura di produzione

DEBUG, SECRET_KEY e ALLOWED_HOSTS

Nel passaggio in produzione devi almeno controllare impostazioni come:

DEBUG = False

e gestire correttamente SECRET_KEY e ALLOWED_HOSTS.

Una SECRET_KEY di produzione non dovrebbe essere pubblicata nel repository.

Una soluzione tipica consiste nel leggerla dall’ambiente:

import os

SECRET_KEY = os.environ["SECRET_KEY"]

Prima del deploy puoi inoltre eseguire:

python manage.py check --deploy

che controlla diverse impostazioni rilevanti per una configurazione di produzione.

Non sostituisce un security audit, ma aiuta a individuare errori comuni.

WSGI e ASGI in produzione

Per un’applicazione principalmente sincrona WSGI rimane un modello valido.

Se il progetto utilizza in modo significativo view asincrone, richieste lunghe o altri workload che beneficiano di uno stack async, ASGI può essere più appropriato.

Entrambe le interfacce sono supportate.

Il punto importante è non scegliere ASGI semplicemente perché “async è più moderno”. Un’architettura asincrona introduce vantaggi soltanto quando il workload e le dipendenze permettono di sfruttarli.

Database, variabili d’ambiente e secret

Password del database, chiavi API e altre credenziali non dovrebbero essere scritte direttamente nel codice destinato al repository.

In produzione servono inoltre:

  • backup;
  • credenziali separate;
  • connessioni configurate correttamente;
  • migrazioni gestite durante il deploy;
  • monitoraggio degli errori;
  • logging appropriato.

Questi aspetti non vengono mostrati nel classico “Hello World”, ma incidono molto più della sintassi della prima view quando l’applicazione diventa reale.

File statici

Durante lo sviluppo runserver semplifica anche la gestione di CSS, JavaScript e immagini statiche.

In produzione il flusso cambia.

È disponibile il comando:

python manage.py collectstatic

per raccogliere gli asset statici in STATIC_ROOT, ma dovrai poi decidere come servirli nell’infrastruttura scelta.

Anche in questo caso il framework gestisce parte del problema, non l’intera piattaforma di deploy.

Docker o PaaS: quando diventano utili

Un’applicazione può essere distribuita attraverso strategie molto differenti.

Docker può aiutare a definire in modo riproducibile runtime, dipendenze e processo dell’applicazione.

Una soluzione PaaS può invece delegare una parte maggiore dell’infrastruttura alla piattaforma.

Nessuna delle due è un prerequisito per imparare il framework.

Aggiungerle durante il primo esercizio rischia anzi di mescolare troppi livelli contemporaneamente:

Python
framework
database
container
networking
deployment

Prima fai funzionare bene l’applicazione. Poi introduci l’infrastruttura necessaria al modo in cui dovrà essere eseguita.

Django vs Flask vs FastAPI: quale scegliere

Django, Flask e FastAPI permettono tutti di costruire software server-side con Python, ma distribuiscono la complessità in modi differenti.

La domanda utile non è quindi “qual è il framework migliore?”.

È:

quali decisioni voglio già integrate e quali preferisco prendere direttamente?

CriterioDjangoFlaskFastAPI
Filosofiaframework integrato con molte convenzionicore leggero e componibileframework orientato soprattutto alle API
Routingintegratointegratointegrato
Template server-sideintegratidisponibili tramite Jinjapossibili, ma non centrali
ORM inclusonono
Migrazioni integrate con ORMrichiedono stack aggiuntivorichiedono stack aggiuntivo
Admin integratonono
Sistema auth completo integratonono
OpenAPI automaticonon nel corenon nel core
Supporto asyncsì, incluso stack ASGI e diverse API asyncasync possibile, core WSGIASGI e async centrali
Libertà iniziale sullo stackminorealtaalta nel dominio API
Struttura inizialepiù esplicitaminimaridotta per una API
Scenario tipicoweb app data-driven e prodotti con molte funzioni integrateservizi e applicazioni dove vuoi comporre lo stackAPI tipizzate e servizi API-first

La tabella non assegna un vincitore.

Mostra dove vengono prese le decisioni.

Quando scegliere Django

Django ha particolarmente senso quando prevedi già molte delle seguenti esigenze:

  • utenti e permessi;
  • database relazionale;
  • back office;
  • form;
  • sessioni;
  • amministrazione dei contenuti;
  • numerosi model collegati;
  • applicazione server-rendered;
  • API integrate in un prodotto più ampio.

In questi casi partire da componenti coerenti può essere più efficiente che progettare ogni integrazione separatamente.

Quando Flask offre più libertà

Flask parte da un nucleo molto più piccolo.

È interessante quando vuoi decidere direttamente database, ORM, struttura applicativa e componenti aggiuntivi oppure quando il servizio non richiede una grande quantità di funzionalità integrate.

Questa libertà è un vantaggio se sai quali decisioni vuoi prendere.

Può diventare invece un costo se devi ricostruire uno stack che un framework più integrato avrebbe già fornito in modo coerente.

Quando FastAPI è più adatto a un progetto API-first

FastAPI è particolarmente convincente quando il prodotto è essenzialmente un’API e vuoi lavorare fin dall’inizio con:

  • type hint Python;
  • validazione degli schemi;
  • OpenAPI automatico;
  • documentazione API generata;
  • architettura ASGI;
  • codice asincrono quando il workload lo richiede.

Se hai bisogno soprattutto di questi elementi e non di admin, template server-side o stack applicativo integrato, partire da una soluzione più ampia può aggiungere componenti che useresti poco.

Dove il framework fa risparmiare lavoro e dove aggiunge struttura

La sua forza e il suo limite nascono dalla stessa scelta: offrire uno stack molto più integrato rispetto a framework minimali.

Dove fa risparmiare decisioni e integrazioni

Il vantaggio emerge quando le funzionalità lavorano insieme.

Un model può essere:

  • gestito attraverso l’ORM;
  • modificato con migrazioni;
  • esposto nell’admin;
  • collegato ai permessi;
  • utilizzato in un ModelForm;
  • interrogato da una view;
  • mostrato in un template.

Non devi decidere ogni volta come far comunicare tecnologie completamente separate.

Questa coerenza diventa particolarmente preziosa con l’aumento della codebase.

Quando le convenzioni diventano un costo

Il rovescio della medaglia è che devi imparare il modo in cui il framework struttura il problema.

Project, app, settings, middleware, URLconf, model, manager, QuerySet, migration, template e form introducono parecchi concetti.

Per una piccola API con due endpoint potresti utilizzare soltanto una frazione dello stack disponibile.

In quel caso una soluzione più piccola può rendere più evidente ciò che sta succedendo e richiedere meno struttura.

Per quali progetti può essere sovradimensionato

Django può avere poco senso quando:

  • devi pubblicare un sito completamente statico;
  • stai costruendo un servizio estremamente piccolo;
  • l’applicazione è esclusivamente un’API minimale;
  • hai già scelto uno stack differente per ORM e infrastruttura;
  • gran parte delle funzionalità integrate rimarrebbe inutilizzata.

“Sovradimensionato” non significa che il framework non funzionerebbe.

Significa che il valore delle convenzioni introdotte non compensa il costo di adottarle.

Gli errori più comuni quando inizi

Molti problemi iniziali non derivano da una API difficile, ma dall’aver mescolato livelli differenti senza comprenderne il ruolo.

Iniziare dal framework senza conoscere abbastanza Python

Puoi completare un tutorial copiando:

from django.shortcuts import render

senza sapere realmente cosa sia un’importazione.

Il problema emerge appena devi modificare l’esempio.

Prima di affrontare seriamente Django conviene saper utilizzare almeno:

  • variabili;
  • condizioni;
  • cicli;
  • funzioni;
  • strutture dati;
  • moduli;
  • classi a livello basilare;
  • eccezioni;
  • ambienti virtuali;
  • pip.

Non serve essere esperti di Python, ma il framework non dovrebbe nascondere completamente il linguaggio su cui è costruito.

Confondere project e app

Creare una nuova app per ogni pagina produce presto una struttura frammentata.

Mettere invece tutto dentro il package del project elimina la separazione messa a disposizione dal framework.

Pensa alle app come unità funzionali coerenti, non come file HTML.

Mettere troppa logica nelle view

Una view può tecnicamente fare moltissime cose.

Questo non significa che debba farle.

Se una view valida input, calcola regole di dominio, interroga dieci model, invia email, chiama servizi esterni e prepara il rendering, diventa difficile da testare e modificare.

Quando la logica cresce, separare responsabilità in funzioni, classi o service layer appropriati rende il comportamento più comprensibile.

Non esiste però una struttura universale da applicare preventivamente a ogni progetto. Introduci nuovi livelli quando risolvono un problema reale di organizzazione.

Modificare i model e dimenticare le migrazioni

Cambiare models.py non modifica automaticamente lo schema del database.

Il normale flusso rimane:

python manage.py makemigrations
python manage.py migrate

Se qualcosa non torna, guarda le migrazioni generate e cerca di capire cosa il framework sta tentando di fare invece di cancellare file casualmente finché l’errore scompare.

Trattare runserver come ambiente di produzione

Il fatto che l’app sia raggiungibile dal browser non significa che sia pronta per essere pubblicata.

runserver appartiene al workflow di sviluppo.

Un deployment reale richiede application server, configurazione, gestione dei segreti, database, HTTPS, static file, logging, backup e una strategia operativa coerente.

Considerare le protezioni integrate come sicurezza automatica

ORM, CSRF middleware, escaping dei template e sistema auth riducono parecchio lavoro delicato.

Possono però essere utilizzati male o aggirati da codice e configurazioni insicure.

La sicurezza dell’applicazione dipende anche da infrastruttura, dipendenze, autorizzazioni, upload, segreti, HTTPS, validazione e aggiornamenti.

Il vantaggio è partire con diversi strumenti corretti già disponibili.

La responsabilità di configurare e mantenere il sistema rimane tua.

Conclusione

Django ha senso quando vuoi costruire un’applicazione web con Python e preferisci partire da uno stack coerente di componenti già integrati invece di scegliere separatamente ORM, autenticazione, migrazioni, form e pannello amministrativo.

Per iniziare bene non serve conoscere tutto il framework. Serve capire il percorso fondamentale:

richiesta
→ URL
→ view
→ model e database quando necessari
→ template o altra risposta
→ browser

Da qui puoi aggiungere autenticazione, form, admin e API senza trattarli come pezzi scollegati.

Se stai ancora imparando il linguaggio, continua prima con programmare in Python. Se invece vuoi capire meglio dove questo framework si colloca nello stack di un’applicazione, approfondisci il funzionamento del backend.

E se il dubbio è soprattutto architetturale, il criterio più utile resta questo: scegli Django quando le sue convenzioni e i componenti integrati eliminano lavoro che dovresti comunque fare; scegli un framework più leggero quando quelle stesse convenzioni diventerebbero struttura inutilizzata.