FastAPI is the modern, increasingly standard choice for ML APIs — built-in automatic input validation via Pydantic, native async support, and auto-generated interactive API documentation, all with minimal extra code compared to Flask.
Full Implementation
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
import joblib
import numpy as np
app = FastAPI(title="Loan Approval API", version="1.2.0")
model = joblib.load("model_pipeline.pkl") # loaded once, at startup
class LoanApplication(BaseModel):
income: float = Field(..., gt=0, description="Annual income")
age: int = Field(..., ge=18, le=100)
credit_score: int = Field(..., ge=300, le=850)
class PredictionResponse(BaseModel):
prediction: str
probability: float
model_version: str
@app.get("/health")
def health():
return {"status": "ok"}
@app.post("/predict", response_model=PredictionResponse)
def predict(application: LoanApplication):
features = np.array([[application.income, application.age, application.credit_score]])
prediction = model.predict(features)[0]
probability = model.predict_proba(features)[0].max()
return PredictionResponse(
prediction=str(prediction),
probability=round(float(probability), 4),
model_version="1.2.0",
)
Automatic Validation — No Manual Checks Needed
Notice there's no manual "check if fields are missing" code anywhere — the LoanApplication Pydantic model declares exactly what's required, its types, and even valid ranges (ge=300, le=850 for credit score). If a request violates any of this, FastAPI automatically returns a clear, structured 422 error before the predict() function's code ever runs.
# A request with credit_score=9999 automatically gets rejected with a detailed error,
# with ZERO manual validation code written -- Pydantic and FastAPI handle it entirely
{
"detail": [
{"loc": ["body", "credit_score"], "msg": "ensure this value is less than or equal to 850", ...}
]
}
Free, Auto-Generated Interactive Documentation
Running the API and visiting /docs automatically produces a full interactive Swagger UI — every endpoint, its expected request/response schema, and a live "try it out" form, generated entirely from the type hints and Pydantic models already in the code, with no separate documentation-writing effort.
Running the API
# uvicorn is the standard ASGI server for FastAPI -- production-ready, unlike Flask's dev server
# uvicorn main:app --host 0.0.0.0 --port 8000
FastAPI vs Flask — The Practical Comparison
| Flask | FastAPI | |
|---|---|---|
| Input validation | Manual | Automatic, via Pydantic |
| API documentation | Manual (or a separate library) | Auto-generated, free |
| Async support | Limited, requires extra setup | Native |
| Learning curve | Slightly gentler for beginners | Slightly steeper, but pays off quickly |
| Production server | Needs a separate WSGI server (Gunicorn) | Uses uvicorn (ASGI), production-ready by default |
Practical Use Cases
- Any new ML API project where there's no strong reason to prefer Flask specifically
- APIs needing strict input validation, especially for high-stakes predictions where malformed input must never reach the model
Common Mistakes
- Loading the model inside the request handler function instead of once at module/startup level — the exact same mistake as in Flask.
- Not defining a
response_model, missing out on FastAPI's automatic response validation and documentation for the output shape too.
Interview Relevance
Q: "Why might you choose FastAPI over Flask for a new ML API today?" Automatic request validation via Pydantic (fewer bugs, less manual validation code), free auto-generated interactive documentation, and native async support with a production-ready ASGI server out of the box — Flask requires manual work or extra libraries to get any of these.
Practice Question
Add a new optional field, employment_years, to the LoanApplication model above, with a sensible type and validation constraint.