Validation
Validation เกิดขึ้นอัตโนมัติ
หัวข้อที่มีชื่อว่า “Validation เกิดขึ้นอัตโนมัติ”ใน Express คุณต้องเลือก validation library (Joi, Zod, class-validator) แล้วเชื่อมต่อเอง ใน FastAPI validation ไม่ใช่ตัวเลือก แต่รันอัตโนมัติสำหรับทุก request ทันทีที่คุณประกาศ typed parameter ถ้า validation ล้มเหลว FastAPI จะส่งคืน 422 Unprocessable Entity พร้อม structured error body ก่อนที่ handler function จะทำงาน
Basic type constraints ด้วย Field
หัวข้อที่มีชื่อว่า “Basic type constraints ด้วย Field”pydantic.Field เทียบเท่ากับ .min(), .max(), .regex() ของ Zod หรือ decorator @IsEmail(), @Min() ของ NestJS
// Zodconst CreateItemSchema = z.object({ name: z.string().min(1).max(100), price: z.number().positive().max(99999), quantity: z.number().int().min(0).default(1), email: z.string().email().optional(), slug: z.string().regex(/^[a-z0-9-]+$/).optional(),});
// class-validator (NestJS)export class CreateItemDto { @IsString() @MinLength(1) @MaxLength(100) name: string; @IsNumber() @IsPositive() @Max(99999) price: number; @IsInt() @Min(0) @IsOptional() quantity?: number = 1; @IsEmail() @IsOptional() email?: string;}# Pydantic with Field constraintsfrom pydantic import BaseModel, Field, EmailStrfrom typing import Optional
class CreateItem(BaseModel): name: str = Field(min_length=1, max_length=100) price: float = Field(gt=0, le=99999) quantity: int = Field(default=1, ge=0) email: Optional[EmailStr] = None # ต้องการ: pip install pydantic[email] slug: Optional[str] = Field( default=None, pattern=r"^[a-z0-9-]+$" )ตาราง Field constraint:
Field kwarg ใน Pydantic | ความหมาย | เทียบเท่าใน Zod |
|---|---|---|
gt=0 | มากกว่า | .positive() |
ge=0 | มากกว่าหรือเท่ากับ | .min(0) |
lt=100 | น้อยกว่า | .max(99) |
le=100 | น้อยกว่าหรือเท่ากับ | .max(100) |
min_length=1 | ความยาวขั้นต่ำ | .min(1) |
max_length=100 | ความยาวสูงสุด | .max(100) |
pattern=r"..." | regex | .regex(...) |
The 422 error response
หัวข้อที่มีชื่อว่า “The 422 error response”เมื่อ validation ล้มเหลว FastAPI จะส่ง structured JSON body อัตโนมัติ ไม่ต้องเพิ่ม middleware และไม่ต้องเขียน try/catch
// Zod การเชื่อมต่อ manualapp.post('/items', (req, res) => { const result = CreateItemSchema.safeParse(req.body); if (!result.success) { return res.status(422).json({ errors: result.error.flatten() }); } // ...});# FastAPI — 422 อัตโนมัติ ไม่ต้องเขียน codefrom fastapi import FastAPIfrom pydantic import BaseModel, Field
app = FastAPI()
class CreateItem(BaseModel): name: str = Field(min_length=1) price: float = Field(gt=0)
@app.post("/items")async def create_item(item: CreateItem): return item # ถึงตรงนี้ได้ก็ผ่าน validation แล้ว
# POST /items {} คืนค่า:# {# "detail": [# {"loc": ["body","name"], "msg":"Field required", "type":"missing"},# {"loc": ["body","price"],"msg":"Field required", "type":"missing"}# ]# }ลองเล่น — manual validation แบบ Pydantic
หัวข้อที่มีชื่อว่า “ลองเล่น — manual validation แบบ Pydantic”รันที่เครื่องตัวเองด้วย FastAPI สำหรับ validation จริง snippet ด้านล่างแสดง logic เดียวกันโดยใช้ stdlib เท่านั้น
from dataclasses import dataclassfrom typing import Optionalimport re
@dataclassclass ValidationError: field: str message: str
def validate_item(data: dict) -> list: errors = []
# name: required, 1-100 chars name = data.get("name", "") if not name: errors.append(ValidationError("name", "Field required")) elif len(name) > 100: errors.append(ValidationError("name", "max_length=100"))
# price: required, must be > 0 price = data.get("price") if price is None: errors.append(ValidationError("price", "Field required")) elif not isinstance(price, (int, float)) or price <= 0: errors.append(ValidationError("price", "gt=0"))
# quantity: optional, must be >= 0 integer qty = data.get("quantity", 1) if not isinstance(qty, int) or qty < 0: errors.append(ValidationError("quantity", "ge=0"))
# slug: optional regex slug = data.get("slug") if slug and not re.match(r"^[a-z0-9-]+$", slug): errors.append(ValidationError("slug", "pattern mismatch"))
return errors
tests = [ {"name": "Widget", "price": 9.99, "quantity": 2, "slug": "widget-pro"}, {}, {"name": "X", "price": -5, "quantity": -1, "slug": "BAD SLUG"},]
for t in tests: errs = validate_item(t) if errs: print(f"INVALID {t}") for e in errs: print(f" [{e.field}] {e.message}") else: print(f"VALID {t}")Loading Python runtime (first run only)…