Django Ninja API endpoint patterns including routers, authentication tiers, typed requests, and Pydantic input/output models. Use when writing or reviewing Django Ninja API endpoints, routers, or auth configuration. Also use when creating new API modules or adding endpoints. Do NOT use for: Django views that return HTML (use django skill), FastAPI, DRF (Django REST Framework), or general Django patterns.
Opinionated conventions for building APIs with Django Ninja.
api.py filesrouter = Router(tags=["module_name"])@router.get(), @router.post(), @router.patch()API endpoint functions are suffixed with _api to avoid name collisions with the
service function they wrap. The service function owns the base name:
# services.py — the real logic, no suffix
def create_user(*, email: str, name: str) -> User:
...
# api.py — thin wrapper, suffixed with _api
@router.post("/v1/users", auth=[require_user], response=UserResponse)
def create_user_api(request: AuthenticatedHttpRequest, input_data: CreateUserInput) -> UserResponse:
user = create_user(email=input_data.email, name=input_data.name)
return UserResponse.from_model(user)
This keeps the import clean — from myapp.services import create_user works without
aliasing. Never rename imports with as to resolve naming conflicts; use the suffix
instead.
Use two auth tiers:
require_user: Endpoints that require authenticationsession_auth: Endpoints that work with both anonymous and authenticated usersMatch the request type annotation to the auth level:
from ninja import Router
router = Router(tags=["users"])
# Authenticated-only endpoint
@router.get("/v1/settings", auth=[require_user], response=UserSettings)
def get_user_settings(request: AuthenticatedHttpRequest) -> UserSettings:
return request.auth.settings
# Mixed auth endpoint (anonymous + authenticated)
@router.post("/v1/feedback", auth=[session_auth], response=StatusResponse)
def create_feedback(
request: AnyUserHttpRequest,
input_data: FeedbackInput,
) -> StatusResponse:
create_feedback_entry(user=request.auth, text=input_data.text)
return StatusResponse(success=True)
# Checking auth state in mixed endpoints
@router.get("/v1/features", auth=[session_auth], response=Features)
def get_features(request: AnyUserHttpRequest) -> Features:
if not request.auth.is_authenticated:
return get_anonymous_features()
return get_user_features(user_id=request.auth.id)
Access the user via request.auth, not request.user.
Inputtypes.py unless used only in one endpoint# types.py
from pydantic import BaseModel
from datetime import datetime
class FeedbackInput(BaseModel):
text: str
page_url: str | None = None
class FeedbackResponse(BaseModel):
id: int
created_at: datetime
| Avoid | Why | Instead |
|---|---|---|
| Business logic in api.py | Can't reuse in tasks or CLI | Call service functions |
request.user | Wrong attribute for Ninja auth | request.auth |
| Untyped request parameter | Loses auth type safety | AuthenticatedHttpRequest or AnyUserHttpRequest |
| Endpoint named same as service function | Import collision, forces ugly as alias | Suffix with _api |
from myapp.services import create_user as _create_user | Obscures the real name, fragile | Name the endpoint create_user_api instead |
| Dict return values | No schema validation | Pydantic response models |
After writing Django Ninja endpoints, verify:
api.py and call service functionsrequest.auth, not request.userInput_api, no import ... as aliasing