Interview question
Build a validated FastAPI create endpoint with safe models
Builds a FastAPI POST boundary with separate input/output models, forbidden extra fields, domain mapping, and a 201 response.
TL;DR
Builds a FastAPI POST boundary with separate input/output models, forbidden extra fields, domain mapping, and a 201 response.
Pydantic validation, separate API models, dependency injection, server-owned fields, response filtering, and HTTP contracts.
Practice the problem like a real interview: restate, reason, implement, and test.
Implement POST /orders. Accept a customer reference and one or more positive-quantity lines, reject unknown fields, and return 201 with a public order model. The client must not be able to set order ID, owner, status, price, or audit fields. Map the request into an application command rather than passing the Pydantic model into persistence.
{"customer_ref":"C-42","lines":[{"sku":"ABC","quantity":2}]}
-> 201 {"id":"...","status":"pending","line_count":1}
from typing import Annotated
from uuid import UUID
from fastapi import APIRouter, Depends, status
from pydantic import BaseModel, ConfigDict, Field
class OrderLineIn(BaseModel):
model_config = ConfigDict(extra="forbid")
sku: str = Field(min_length=1, max_length=64)
quantity: int = Field(gt=0, le=100)
class CreateOrderIn(BaseModel):
model_config = ConfigDict(extra="forbid")
customer_ref: str = Field(min_length=1, max_length=80)
lines: list[OrderLineIn] = Field(min_length=1, max_length=50)
class OrderOut(BaseModel):
id: UUID
status: str
line_count: int
router = APIRouter()
@router.post("/orders", response_model=OrderOut, status_code=status.HTTP_201_CREATED)
async def create_order(
body: CreateOrderIn,
actor: Annotated[Principal, Depends(current_principal)],
service: Annotated[OrderService, Depends(order_service)],
) -> OrderOut:
command = CreateOrder(
owner_id=actor.user_id,
customer_ref=body.customer_ref,
lines=[CreateLine(sku=x.sku, quantity=x.quantity) for x in body.lines],
)
created = await service.create(command)
return OrderOut(id=created.id, status=created.status, line_count=len(created.lines))
Validation and mapping are O(number of lines). Persistence cost is owned by the application service and should remain one bounded transaction.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Practice the next layer of the same subject.