Many FastAPI APIs store fields that could be computed instead — nights, total price — then recalculate them by hand in the endpoint, the service, and the frontend. Miss one place and the numbers drift apart. With Pydantic v2, you do not need to store or compute those fields by hand: use @computed_field so a field derives itself from the other fields, and @model_validator(mode="after") to validate the whole model at once — use it when a field only makes sense next to another field, like an end date that must come after the start date.

The problem

Suppose you build a booking API. The client sends a start date, an end date, and a price per night, but you also store the number of nights and the total, computing them in the endpoint. That is redundant, easy to get out of sync when one place changes and another is forgotten, and the date validation ends up scattered instead of living in the model. Worse, if the client sends a wrong number of nights, the server may trust it and store it as-is.

# Cách dễ lệch: tính tay trong endpoint, không có gì chặn ngày sai
@app.post("/bookings")
def create_booking(data: dict):
nights = (data["end_date"] - data["start_date"]).days
total = nights * data["price_per_night"]
return {"nights": nights, "total": total}

The code above does not stop an end date that comes before the start date — negative nights and a negative total are returned as if nothing were wrong.

The solution

Move all the derivation and cross-checking logic into the Pydantic model itself. A computed field is marked with @computed_field, and cross-field checks use @model_validator(mode="after") — “after” mode runs once every field has been validated, so you work with the complete model, not raw input data.

from datetime import date
from pydantic import BaseModel, Field, computed_field, model_validator
class BookingCreate(BaseModel):
start_date: date
end_date: date
price_per_night: float = Field(gt=0)
@computed_field
@property
def nights(self) -> int:
return (self.end_date - self.start_date).days
@computed_field
@property
def total(self) -> float:
return round(self.nights * self.price_per_night, 2)
@model_validator(mode="after")
def check_dates(self):
if self.end_date <= self.start_date:
raise ValueError("end_date phải sau start_date")
return self

When you use this model in a FastAPI endpoint, everything runs on its own: the client sends the three input fields, the response automatically includes the correctly computed nights and total, and a bad date pair is rejected at the gate with a 422 error, without a single extra check in the endpoint. One thing worth noting: a computed field is not an input field — if the client tries to send a wrong nights value, Pydantic still recomputes it from the real dates. The validator must return self; forget that and the model is replaced by None, an error that is very hard to trace.

from fastapi import FastAPI
app = FastAPI()
@app.post("/bookings", status_code=201)
def create_booking(data: BookingCreate):
# data.nights và data.total đã có sẵn, đã validate xong
return data

Applying it to your project

The rule of thumb is simple: if a value can always be derived from other fields in the same model, let @computed_field compute it — do not store it or compute it by hand. If a rule only holds when several fields are considered together — the end date after the start date, the repeated password matching — put it in @model_validator(mode="after"). A check that concerns only one field, such as a price that must be greater than zero, still belongs to Field or @field_validator. Keep computed fields pure — only calculations, no database or external API calls.

Related posts

FAQ

How is computed_field different from a plain @property?

A plain @property does not appear in model_dump() or in FastAPI’s JSON response, while a @computed_field does — the client receives the computed field like any ordinary field in the response.

Can the client send a computed field to override it?

No. A computed field is not an input field, so any value the client sends for it is ignored and Pydantic recomputes it from the real data.

What is the difference between mode=”before” and mode=”after”?

“Before” mode receives raw data before validation, which suits normalizing input. “After” mode receives the model after validation, which suits cross-field checks — the mode you need for the date example above.

When should I use model_validator instead of field_validator?

Use @field_validator when a rule concerns only one field. Use @model_validator when a rule needs to compare several fields with each other, because only then do you have the whole model in hand.

💬 Have you tried this in your own project? If you hit an error or know a cleaner way, leave a comment below — I read them all and will fold good ideas into the post.

Sources