django-dev-ninja

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Django Ninja API Development

Django Ninja API开发

Opinionated Django Ninja patterns with single-endpoint-per-file organization.
采用“每个文件对应一个端点”组织方式的Django Ninja约定式开发模式。

Core Principles

核心原则

  1. One endpoint = one file - Each endpoint lives in its own file
  2. Logical grouping - Endpoints grouped in subpackages by domain
  3. Router per group - Each group has its own router
  4. Schemas in separate package - Pydantic models in
    schemas/
  5. Services for logic - Business logic in services, not endpoints
  1. 一个端点对应一个文件 - 每个端点单独存放在一个文件中
  2. 按领域逻辑分组 - 端点按业务领域划分子包进行分组
  3. 每组一个路由 - 每个业务组拥有独立的Router
  4. Schema独立分包 - Pydantic模型存放在
    schemas/
    目录下
  5. 业务逻辑抽离至服务层 - 业务逻辑实现于服务层,而非端点中

API Structure

API结构

myapp/
├── api/
│   ├── __init__.py           # Main NinjaAPI instance
│   ├── users/
│   │   ├── __init__.py       # Router: users_router
│   │   ├── list.py           # GET /users/
│   │   ├── detail.py         # GET /users/{id}
│   │   ├── create.py         # POST /users/
│   │   ├── update.py         # PUT /users/{id}
│   │   └── delete.py         # DELETE /users/{id}
│   ├── products/
│   │   ├── __init__.py
│   │   ├── list.py
│   │   ├── detail.py
│   │   └── search.py
│   └── auth/
│       ├── __init__.py
│       ├── login.py
│       ├── logout.py
│       └── refresh.py
└── schemas/
    ├── __init__.py
    ├── user.py               # UserIn, UserOut, UserPatch
    ├── product.py
    └── common.py             # Pagination, errors
myapp/
├── api/
│   ├── __init__.py           # 主NinjaAPI实例
│   ├── users/
│   │   ├── __init__.py       # 路由:users_router
│   │   ├── list.py           # GET /users/
│   │   ├── detail.py         # GET /users/{id}
│   │   ├── create.py         # POST /users/
│   │   ├── update.py         # PUT /users/{id}
│   │   └── delete.py         # DELETE /users/{id}
│   ├── products/
│   │   ├── __init__.py
│   │   ├── list.py
│   │   ├── detail.py
│   │   └── search.py
│   └── auth/
│       ├── __init__.py
│       ├── login.py
│       ├── logout.py
│       └── refresh.py
└── schemas/
    ├── __init__.py
    ├── user.py               # UserIn, UserOut, UserPatch
    ├── product.py
    └── common.py             # 分页、错误处理Schema

Main API Setup

主API配置

In
api/__init__.py
:
python
from ninja import NinjaAPI
from ninja.security import HttpBearer

from .users import router as users_router
from .products import router as products_router
from .auth import router as auth_router


class AuthBearer(HttpBearer):
    def authenticate(self, request, token):
        # Token validation logic
        from ..services.auth import AuthService
        return AuthService.validate_token(token)


api = NinjaAPI(
    title="My API",
    version="1.0.0",
    description="API documentation",
    auth=AuthBearer(),
)
api/__init__.py
中:
python
from ninja import NinjaAPI
from ninja.security import HttpBearer

from .users import router as users_router
from .products import router as products_router
from .auth import router as auth_router


class AuthBearer(HttpBearer):
    def authenticate(self, request, token):
        # Token验证逻辑
        from ..services.auth import AuthService
        return AuthService.validate_token(token)


api = NinjaAPI(
    title="My API",
    version="1.0.0",
    description="API文档",
    auth=AuthBearer(),
)

Register routers

注册路由

api.add_router("/users", users_router, tags=["Users"]) api.add_router("/products", products_router, tags=["Products"]) api.add_router("/auth", auth_router, tags=["Authentication"], auth=None)
undefined
api.add_router("/users", users_router, tags=["Users"]) api.add_router("/products", products_router, tags=["Products"]) api.add_router("/auth", auth_router, tags=["Authentication"], auth=None)
undefined

Router Setup

路由配置

Each group has a router in
__init__.py
:
python
undefined
每个业务组在
__init__.py
中定义路由:
python
undefined

api/users/init.py

api/users/init.py

from ninja import Router
from .list import router as list_router from .detail import router as detail_router from .create import router as create_router from .update import router as update_router from .delete import router as delete_router
router = Router()
from ninja import Router
from .list import router as list_router from .detail import router as detail_router from .create import router as create_router from .update import router as update_router from .delete import router as delete_router
router = Router()

Merge endpoint routers

合并端点路由

router.add_router("", list_router) router.add_router("", detail_router) router.add_router("", create_router) router.add_router("", update_router) router.add_router("", delete_router)
undefined
router.add_router("", list_router) router.add_router("", detail_router) router.add_router("", create_router) router.add_router("", update_router) router.add_router("", delete_router)
undefined

Endpoint File Template

端点文件模板

Each endpoint in its own file:
python
undefined
每个端点单独存放在一个文件中:
python
undefined

api/users/create.py

api/users/create.py

from ninja import Router from django.http import HttpRequest
from ...schemas.user import UserIn, UserOut from ...services.user import UserService
router = Router()
@router.post("/", response={201: UserOut}) def create_user(request: HttpRequest, payload: UserIn) -> UserOut: """Create a new user.""" user = UserService.create(payload) return user
undefined
from ninja import Router from django.http import HttpRequest
from ...schemas.user import UserIn, UserOut from ...services.user import UserService
router = Router()
@router.post("/", response={201: UserOut}) def create_user(request: HttpRequest, payload: UserIn) -> UserOut: """创建新用户。""" user = UserService.create(payload) return user
undefined

Schema Organization

Schema组织

Pydantic schemas in
schemas/
package:
python
undefined
Pydantic Schema存放在
schemas/
包中:
python
undefined

schemas/user.py

schemas/user.py

from uuid import UUID from datetime import datetime from pydantic import BaseModel, EmailStr, Field
class UserBase(BaseModel): """Shared user fields.""" email: EmailStr name: str = Field(max_length=255)
class UserIn(UserBase): """Input schema for creating users.""" password: str = Field(min_length=8)
class UserPatch(BaseModel): """Partial update schema.""" email: EmailStr | None = None name: str | None = Field(None, max_length=255)
class UserOut(UserBase): """Output schema for users.""" id: UUID is_active: bool created_at: datetime
class Config:
    from_attributes = True
undefined
from uuid import UUID from datetime import datetime from pydantic import BaseModel, EmailStr, Field
class UserBase(BaseModel): """用户共享字段。""" email: EmailStr name: str = Field(max_length=255)
class UserIn(UserBase): """创建用户的输入Schema。""" password: str = Field(min_length=8)
class UserPatch(BaseModel): """部分更新Schema。""" email: EmailStr | None = None name: str | None = Field(None, max_length=255)
class UserOut(UserBase): """用户输出Schema。""" id: UUID is_active: bool created_at: datetime
class Config:
    from_attributes = True
undefined

Common Patterns

通用模式

Pagination

分页

python
undefined
python
undefined

schemas/common.py

schemas/common.py

from typing import Generic, TypeVar, List from pydantic import BaseModel
T = TypeVar("T")
class PaginatedResponse(BaseModel, Generic[T]): items: List[T] total: int page: int per_page: int pages: int

```python
from typing import Generic, TypeVar, List from pydantic import BaseModel
T = TypeVar("T")
class PaginatedResponse(BaseModel, Generic[T]): items: List[T] total: int page: int per_page: int pages: int

```python

api/users/list.py

api/users/list.py

from ninja import Router, Query from ...schemas.user import UserOut from ...schemas.common import PaginatedResponse
router = Router()
@router.get("/", response=PaginatedResponse[UserOut]) def list_users( request, page: int = Query(1, ge=1), per_page: int = Query(20, ge=1, le=100), ): """List users with pagination.""" from ...services.user import UserService return UserService.list_paginated(page, per_page)
undefined
from ninja import Router, Query from ...schemas.user import UserOut from ...schemas.common import PaginatedResponse
router = Router()
@router.get("/", response=PaginatedResponse[UserOut]) def list_users( request, page: int = Query(1, ge=1), per_page: int = Query(20, ge=1, le=100), ): """分页列出用户。""" from ...services.user import UserService return UserService.list_paginated(page, per_page)
undefined

Error Handling

错误处理

python
undefined
python
undefined

schemas/common.py

schemas/common.py

class ErrorResponse(BaseModel): detail: str code: str | None = None
class ValidationErrorResponse(BaseModel): detail: list[dict]

```python
class ErrorResponse(BaseModel): detail: str code: str | None = None
class ValidationErrorResponse(BaseModel): detail: list[dict]

```python

api/users/detail.py

api/users/detail.py

from ninja import Router from django.http import Http404 from ...schemas.user import UserOut from ...schemas.common import ErrorResponse
router = Router()
@router.get("/{user_id}", response={200: UserOut, 404: ErrorResponse}) def get_user(request, user_id: UUID): """Get user by ID.""" from ...services.user import UserService
user = UserService.get_by_id(user_id)
if not user:
    return 404, {"detail": "User not found", "code": "USER_NOT_FOUND"}
return user
undefined
from ninja import Router from django.http import Http404 from ...schemas.user import UserOut from ...schemas.common import ErrorResponse
router = Router()
@router.get("/{user_id}", response={200: UserOut, 404: ErrorResponse}) def get_user(request, user_id: UUID): """根据ID获取用户。""" from ...services.user import UserService
user = UserService.get_by_id(user_id)
if not user:
    return 404, {"detail": "用户未找到", "code": "USER_NOT_FOUND"}
return user
undefined

File Upload

文件上传

python
undefined
python
undefined

api/files/upload.py

api/files/upload.py

from ninja import Router, File, UploadedFile from ...schemas.file import FileOut
router = Router()
@router.post("/upload", response=FileOut) def upload_file(request, file: UploadedFile = File(...)): """Upload a file.""" from ...services.file import FileService return FileService.save(file)
undefined
from ninja import Router, File, UploadedFile from ...schemas.file import FileOut
router = Router()
@router.post("/upload", response=FileOut) def upload_file(request, file: UploadedFile = File(...)): """上传文件。""" from ...services.file import FileService return FileService.save(file)
undefined

URL Configuration

URL配置

Register API in
urls.py
:
python
undefined
urls.py
中注册API:
python
undefined

config/urls.py

config/urls.py

from django.urls import path from apps.myapp.api import api
urlpatterns = [ path("api/", api.urls), ]
undefined
from django.urls import path from apps.myapp.api import api
urlpatterns = [ path("api/", api.urls), ]
undefined

Authentication Patterns

认证模式

See
references/endpoints.md
for detailed authentication patterns including:
  • JWT authentication
  • API key authentication
  • Session authentication
  • Permission decorators
查看
references/endpoints.md
获取详细的认证模式,包括:
  • JWT认证
  • API密钥认证
  • Session认证
  • 权限装饰器

Additional Resources

额外资源

Reference Files

参考文件

  • references/endpoints.md
    - Detailed endpoint patterns, authentication, permissions
  • references/routers.md
    - Router organization, versioning, OpenAPI customization
  • references/endpoints.md
    - 详细的端点模式、认证、权限说明
  • references/routers.md
    - 路由组织、版本控制、OpenAPI自定义说明

Related Skills

相关技能

  • django-dev - Core Django patterns and model organization
  • django-dev-test - Testing API endpoints with pytest
  • django-dev - 核心Django模式与模型组织
  • django-dev-test - 使用pytest测试API端点