Skip to content

Request Body & Validation

JustAPI validates request bodies against schemas defined with Pydantic models or the built-in Schema class. Validation runs in Rust by default, keeping the GIL released.

from justapi import JustAPIApp
from pydantic import BaseModel
app = JustAPIApp()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.post("/items/")
def create_item(request, item: Item):
item_dict = item.model_dump()
if item.tax:
item_dict["price_with_tax"] = item.price + item.tax
return item_dict
Terminal window
curl -X POST http://127.0.0.1:8000/items/ \
-H "Content-Type: application/json" \
-d '{"name": "Laptop", "price": 999.99, "tax": 50.0}'
# Output: {"name":"Laptop","description":null,"price":999.99,"tax":50.0,"price_with_tax":1049.99}

Invalid data returns 422 with details:

Terminal window
curl -X POST http://127.0.0.1:8000/items/ \
-H "Content-Type: application/json" \
-d '{"name": "Laptop", "price": "free"}'
# Output: {"detail":"validation error for price: invalid type"}

Using body_schema for Rust-Side Validation

Section titled “Using body_schema for Rust-Side Validation”

For maximum performance, pass a schema explicitly via body_schema. Validation runs entirely in Rust before Python is called:

from justapi import JustAPIApp, Schema
from pydantic import Field
class ProductCreate(Schema):
name: str = Field(..., min_length=1, max_length=100)
price: float = Field(..., gt=0, le=100000)
quantity: int = Field(0, ge=0)
@app.post("/products/", body_schema=ProductCreate)
def create_product(request):
data = request.json()
return {"status": "created", "product": data}

For the absolute fastest path (724,000+ RPS), use native=True. The entire handler runs in Rust — no Python bytecode execution:

@app.post("/fast-items/", body_schema=ProductCreate, native=True)
def fast_create(request):
return {"status": "ok"}
Approach RPS p50 Latency
Python handler + Pydantic ~60,000 0.8 ms
body_schema (Rust validates) ~280,000 0.2 ms
native=True (full Rust) ~724,000 0.07 ms

The built-in Schema class supports field constraints with Field():

from justapi import JustAPIApp, Schema
from pydantic import Field
class UserCreate(Schema):
username: str = Field(..., min_length=3, max_length=50, regex=r"^\w+$")
email: str = Field(..., format="email")
age: int = Field(..., ge=18, le=120)
role: str = Field("user", enum=["user", "admin", "moderator"])
Constraint Type Description
gt number Greater than
ge number Greater than or equal
lt number Less than
le number Less than or equal
min_length int Minimum string length
max_length int Maximum string length
regex str Regex pattern match
format str Named format (email, uri, uuid, date, date-time, hostname, ipv4)
enum list Allowed values
default any Default value
description str Field description for OpenAPI
class Address(Schema):
street: str
city: str
zip_code: str = Field(..., regex=r"^\d{5}(-\d{4})?$")
class Customer(Schema):
name: str
address: Address
tags: list[str]

The nested Address schema is lifted into $defs and referenced via $ref in the generated JSON Schema. Validation runs entirely in Rust.

By default, request bodies are limited to 50 MiB. You can configure this:

app.run("0.0.0.0:8000", max_body_size=1024 * 1024) # 1 MiB cap

Bodies exceeding the limit receive 413 Payload Too Large immediately.