Type hints are Python annotations that tell tools what kinds of values to expect. In Django Ninja, they do more than document intent: schemas use them to declare serializable fields, endpoint decorators use them to describe responses, and filters use them to identify query input. Pydantic underpins this schema system, giving annotated classes a dataclass-like shape. section 11.2.1 section 11.5
A schema can expose selected model fields without making the Django model itself JSON-serializable. Subclass ModelSchema, point Meta.model at the ORM model, and list the fields to include. An endpoint response annotation of list[PromoterSchema] then declares that the route returns multiple serialized promoters. section 11.2.1
Listing 11.4 Serialize an ORM Model class with Ninja ModelSchema
# RiffMates/promoters/api.py
from ninja import Router, ModelSchema
from promoters.models import Promoter
router = Router() #1
class PromoterSchema(ModelSchema): #2
class Meta:
model = Promoter #3
fields = ["id", "full_name", "birth", "death"] #4
@router.get("/promoters/",
response=list[PromoterSchema]) #5
def promoters(request):
return Promoter.objects.all() #6
This endpoint returns Django ORM objects while its response type tells Ninja to serialize each object through PromoterSchema. section 11.2.1
Hints also define request-side API behavior. A FilterSchema uses annotated fields to state which values may filter a listing. Optional[str] marks a parameter as not required; Field adds details such as the Django lookup to use. The endpoint parameter is annotated as VenueFilter and given Query(...) so Ninja treats it as query filtering rather than an ordinary value. section 11.2.3 section 11.5
Listing 11.9 Declare the fields that participate in a Ninja filter
# RiffMates/bands/api.py
...
from typing import Optional
...
from ninja import Field, Router, ModelSchema,
FilterSchema #1
...
class VenueFilter(FilterSchema): #2
name: Optional[str] = Field(None,
q=['name__istartswith']) #3
The name annotation declares an optional string filter, while Field connects it to a case-insensitive starts-with Django lookup. section 11.2.3
Common annotation patterns in API schemas include:
- list[Schema] for an endpoint response or field containing multiple serialized objects. section 11.2.1 section 11.5
- Optional[str] for a query value that callers may omit. section 11.2.3
- Field(...) when a declaration needs a required placeholder, default, alias, or other field-level control. section 11.2.3 section 11.5
Nested output combines a nested schema with a collection hint. Declaring rooms: list[RoomSchema] says that a venue contains multiple room objects. Field(..., alias="room_set") maps Django’s related-object manager name to the clearer API field name rooms, and Ninja performs the related subquery during serialization. section 11.2.4 section 11.5
Listing 11.11 Nesting Room data inside serialized Venue objects
# RiffMates/bands/api.py
...
from bands.models import Venue, Room #1
...
class RoomSchema(ModelSchema): #2
class Meta:
model = Room
fields = ["id", "name"]
class VenueOut(ModelSchema):
slug: str
url: str
rooms: list[RoomSchema] = Field(...,
alias="room_set") #3
...
The RoomSchema and rooms: list[RoomSchema] annotation turn a venue’s related rooms into nested API data while aliasing room_set to rooms. section 11.2.4
Optional venue-name filter
A venue listing can accept filters: VenueFilter = Query(...). Inside VenueFilter, name: Optional[str] permits requests with no name while Field(None, q=["name__istartswith"]) configures a case-insensitive starts-with lookup. The endpoint applies filters.filter(venues) before returning the result. section 11.2.3
















