FastAPI is the fastest-growing Python web framework for building APIs, and it’s become the standard choice for data scientists who need to expose machine learning models as REST APIs. It’s fast (async by default), self-documenting (automatic Swagger UI), and catches bugs before they hit production (Pydantic validation). This guide shows you how to go from trained model to deployed API.
Why FastAPI for ML Serving?
Flask was the go-to for ML APIs for years, but FastAPI has overtaken it for several reasons. It’s 2-3× faster than Flask for I/O-bound tasks due to async support. Type annotations and Pydantic automatically validate request data and generate interactive documentation. And it’s production-ready out of the box with ASGI servers like Uvicorn. For any new ML API project in 2026, FastAPI is the right choice.
Installation
pip install fastapi uvicorn pydantic scikit-learn pandas
Your First FastAPI ML API
# app.py
from fastapi import FastAPI
from pydantic import BaseModel, Field
import pickle
import numpy as np
app = FastAPI(title="House Price Predictor", version="1.0")
# Load model at startup
with open("model.pkl", "rb") as f:
model = pickle.load(f)
class HouseFeatures(BaseModel):
size_sqft: float = Field(..., gt=0, description="Size in square feet")
bedrooms: int = Field(..., ge=1, le=10)
bathrooms: float = Field(..., ge=1.0)
age_years: int = Field(..., ge=0)
location_score: float = Field(..., ge=0, le=10)
class PredictionResponse(BaseModel):
predicted_price: float
confidence_interval: dict
@app.get("/health")
def health_check():
return {"status": "ok", "model": "house-price-v1"}
@app.post("/predict", response_model=PredictionResponse)
def predict(features: HouseFeatures):
X = np.array([[features.size_sqft, features.bedrooms,
features.bathrooms, features.age_years,
features.location_score]])
price = float(model.predict(X)[0])
return PredictionResponse(
predicted_price=round(price, 2),
confidence_interval={"lower": round(price * 0.92, 2),
"upper": round(price * 1.08, 2)})
Running the API
uvicorn app:app --reload --host 0.0.0.0 --port 8000
Open http://localhost:8000/docs for the automatic Swagger UI — you can test your endpoint directly in the browser with no extra setup.
Batch Predictions
from typing import List
import pandas as pd
@app.post("/predict/batch")
def predict_batch(features: List[HouseFeatures]):
df = pd.DataFrame([f.dict() for f in features])
prices = model.predict(df.values)
return {"predictions": [round(float(p), 2) for p in prices],
"count": len(prices)}
Background Tasks and Async
from fastapi import BackgroundTasks
import logging
def log_prediction(features: dict, prediction: float):
logging.info(f"Prediction: {prediction} | Input: {features}")
@app.post("/predict/logged")
async def predict_logged(features: HouseFeatures, bg: BackgroundTasks):
X = np.array([[features.size_sqft, features.bedrooms,
features.bathrooms, features.age_years,
features.location_score]])
price = float(model.predict(X)[0])
bg.add_task(log_prediction, features.dict(), price) # runs after response
return {"predicted_price": round(price, 2)}
Model Loading with Lifespan Events
from contextlib import asynccontextmanager
ml_models = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: load model
with open("model.pkl", "rb") as f:
ml_models["house_price"] = pickle.load(f)
yield
# Shutdown: cleanup
ml_models.clear()
app = FastAPI(lifespan=lifespan)
This pattern is better than global variables — the model loads once at startup and is available to all route handlers via the shared dict.
Dockerizing the FastAPI App
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
docker build -t house-price-api .
docker run -p 8000:8000 house-price-api
Adding Authentication
from fastapi.security import APIKeyHeader
from fastapi import Security, HTTPException
API_KEY = "your-secret-api-key"
api_key_header = APIKeyHeader(name="X-API-Key")
async def verify_api_key(key: str = Security(api_key_header)):
if key != API_KEY:
raise HTTPException(status_code=403, detail="Invalid API key")
@app.post("/predict/secure", dependencies=[Depends(verify_api_key)])
def predict_secure(features: HouseFeatures):
...
Conclusion
FastAPI makes building production-ready ML APIs genuinely enjoyable. The automatic Swagger docs reduce your need to write API documentation, Pydantic catches bad input before your model sees it, and async support means you can handle many concurrent requests efficiently. From training to a Dockerized, authenticated API with interactive docs — it’s a few dozen lines of Python.



