
Django Ninja: la "Very Fast API" che unisce Django e FastAPI (PyVenice #3)
Al PyVenice #3, serata a tema framework ospitata all’Anda Venice Hostel, ho parlato di Django Ninja. Il titolo del talk era volutamente un gioco di parole: Django Ninja — Very Fast API, cioè Django più FastAPI. In questo post provo a riassumere il perché.
Il video completo della serata è su YouTube: PyVenice #3 – Flask & Django Ninja.
Il punto di partenza: due ottimi framework, nessuno dei due perfetto
Django è probabilmente il framework Python più conosciuto e usato. Per fare API, il suo modo “standard” è Django REST Framework (DRF). È un framework eccezionale, fa tutto e lo fa bene — ma a me, onestamente, sta un po’ stretto.
Dall’altra parte c’è FastAPI, che nel frattempo è arrivato in auge. Fa solo API (niente template, niente form), ma le fa bene e si appoggia a Pydantic per il parsing e soprattutto la validazione dei dati.
La domanda che mi sono fatto è semplice: e se prendessimo il meglio dei due? Qualcuno ci ha pensato — probabilmente come me — e ha creato Django Ninja: un’estensione di Django ispirata a FastAPI, che porta dentro Django la comodità e la velocità di scrittura delle API in stile FastAPI.
La prima API
Il funzionamento è immediato: si istanzia una classe NinjaAPI e si decorano le funzioni per dirle a che URL rispondere.
api = NinjaAPI()
@api.get("/hello")
def hello(request):
return {"message": "Hello"}
Poi, come in qualsiasi progetto Django, si registra il modulo nel urls.py aggiungendo il path e dicendo a Django di usare quell’app. Il risultato è un’API che, con una manciata di righe, risponde già su /hello.
Passare parametri: query, path e body
Con un endpoint vuoto non si va lontano. I dati si possono passare in tre modi principali.
Query string. Basta aggiungere il parametro alla firma della funzione:
@api.get("/hello")
def hello(request, name: str = "world"):
return {"message": f"Hello {name}"}
Il tipo (str) e il valore di default dicono a Ninja come fare il parsing e cosa fare se il parametro manca.
Path parameter. Si dichiara nel decoratore e il valore arriva come parametro:
@api.get("/hello/{name}")
def hello(request, name: str):
return {"message": f"Hello {name}"}
Body. Quando i dati sono tanti, l’URL ha una dimensione limitata e non è il posto giusto: si usa il body. Qui entra in gioco la parte in stile FastAPI: si descrive la forma del body estendendo la classe Schema (Pydantic), e Ninja valida automaticamente la richiesta, segnalando con precisione dove non torna. Gli schemi si possono annidare uno dentro l’altro per descrivere strutture complesse.
class BookIn(Schema):
name: str
@api.post("/books")
def create_book(request, data: BookIn):
...
Allo stesso modo si possono definire le risposte: non solo cosa restituire quando tutto va bene (200), ma anche, per esempio, il tipo di risposta in caso di errore (404, 401…), con strutture diverse per ciascun caso.
Dentro il database: ModelSchema e Django ORM
La parte più interessante è l’integrazione con il resto di Django. Django non è solo un sistema per pubblicare contenuti: sotto ha un ORM e un modo per descrivere i dati tramite i model. Sarebbe comodo prenderli e “spararli fuori” così come sono — e si può fare, con i ModelSchema.
class BookSchema(ModelSchema):
class Meta:
model = Book
fields = ["title", "author"]
Con una sintassi simile a quella che si vede nell’admin di Django, il ModelSchema genera lo schema e fa il parsing in base al tipo di campo: un CharField, un EmailField, un intero vengono validati di conseguenza.
Piccola attenzione: spesso non vuoi pubblicare tutti i campi (pensa a una password). Puoi elencare solo quelli che ti servono. E c’è un dettaglio utile: se esponi una relazione, di default esce la foreign key (l’ID dell’autore). Puoi invece sovrascrivere quel campo con uno schema annidato, così ottieni direttamente l’oggetto autore completo:
class BookSchema(ModelSchema):
author: AuthorSchema | None = None
class Meta:
model = Book
fields = ["title"]
Il risultato è un JSON su più livelli (edizione → libro → autore) costruito con pochissimo sforzo: hai solo definito i model come faresti normalmente e gli hai detto quali usare.
Per la query si usa il solito ORM di Django, ad esempio con un filtro case-insensitive:
@api.get("/books/{title}")
def search_book(request, title: str):
books = Book.objects.filter(title__icontains=title)
if not books:
return 404, {"message": "Not found"}
return books
Nessun passaggio manuale: gli oggetti del model vengono serializzati direttamente.
Autenticazione e documentazione
L’autenticazione riusa quella di Django: si passa un parametro auth all’API, e lo stesso vale per tutte le API costruite da quella istanza. In alternativa si può estendere la classe di autenticazione (per esempio HttpBearer) e implementare il proprio metodo authenticate, così da gestire token o schemi custom.
Infine la documentazione automatica, disponibile in due forme: Swagger e il meno conosciuto ReDoc. Ninja costruisce da solo lo openapi.json e, cosa notevole, recupera anche i vincoli dei model — ad esempio il max_length di un campo — senza che tu debba riscriverli nello schema. Puoi provare le chiamate direttamente dalla UI.
Perché mi è piaciuto
In sintesi: Django Ninja mette insieme la velocità di scrittura e la leggibilità di FastAPI con la potenza del database e dell’ORM di Django. Con DRF un lavoro così richiede, secondo me, un po’ più di codice. Con Ninja lo sforzo si concentra quasi tutto nel definire bene i model, e il resto è in gran parte automatico e tipizzato.
Le domande (e una precisazione)
Dalle domande in sala sono emersi un paio di punti:
- Async? Django è nato sincrono e lo resta; nelle versioni recenti supporta ASGI e l’async, e Django Ninja funziona anche in quel contesto (l’ho usato, ad esempio, con i WebSocket per caricare file grandi). Per il resto gira benissimo anche in modalità sincrona.
- È più veloce di FastAPI? No, non c’è paragone: FastAPI, da solo, è un’altra cosa. Il valore di Django Ninja non è battere FastAPI, ma avere il suo stile dentro Django, senza rinunciare all’ecosistema e al database del framework.
Il resto della serata
La serata si apriva con Giorgio Basile e il suo “Da Flask ad Angular”: l’avventura di due studenti nella creazione di una web app con Flask, tra server-side rendering (con Jinja), client-side rendering (con Angular), il concetto di sessione e l’accesso al database tramite SQLAlchemy. Anche quella parte merita una visione.
In conclusione
Se usi Django e ti serve costruire API, Django Ninja è una bella scorciatoia: rimani nel tuo ecosistema, ma scrivi API con la praticità di FastAPI e la validazione di Pydantic. Il Very Fast del titolo non è (solo) una battuta.
Il video integrale è online. Grazie a PyVenice e a chi è passato a sentirlo dal vivo.