מבוא ל-FastAPI: למה לבחור אותו?

FastAPI הוא מסגרת פיתוח API מודרנית לבסיס Python, שנוצרה על ידי סבסטיאן רמז. היא מתמקדת ביצירת API מהירים, בטוחים וקלים לתיעוד — תוך שימוש במתקנים מובנים של Python 3.6+ כגון סוגי טיפוסים, Pydantic ואובייקטים של איחוד (Union).

מהירות יוצאת דופן

FastAPI נבנתה על בסיס Starlette (למוניטורינג ו-ASGI) ו-Pydantic (לאימות). היא אחת מהמסגרות המהירות ביותר בעולם Python — מהירה פי 2–3 מ-Flask או Django REST Framework בבדיקות עומסים.

תיעוד אוטומטי מלא

בזמן שמריצים את ה-API, FastAPI מייצרת אוטומטית שני סוגי תיעוד:

  • Swagger UI בכתובת /docs
  • ReDoc בכתובת /redoc
הערה טכנית: FastAPI משתמשת בארכיטקטורת ASGI (Asynchronous Server Gateway Interface), לא WSGI. זה מאפשר תמיכה טבעית ב-tasks אסינכרוניים, مثل קריאות למסדי נתונים, שירותים חיצוניים או עיבוד קבצים — בלי לחסום את ה-thread הראשי.

התקנה והגדרת פרויקט ראשון

נתחיל בהתקנת FastAPI יחד עם השרת המובנה שלה — Uvicorn. אין צורך בשרת נפרד כמו Gunicorn או uWSGI בשלב ההתחלתי.

# יצירת סביבת וירטואלית (מומלץ)
python -m venv fastapi-env
source fastapi-env/bin/activate  # ב-Mac/Linux
# או ב-Windows:
# fastapi-env\Scripts\activate

# התקנת החבילות
pip install fastapi uvicorn[standard]

ניצור קובץ בשם main.py:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "ברוך הבא ל-API שלך עם FastAPI!"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

נריץ את השרת:

# מריץ את השרת באישור localhost:8000
uvicorn main:app --reload

כעת ניתן לגשת ל:

עיצוב מבנה פרויקט מקצועי

כשמפתחים פרויקט אמיתי, חשוב להפריד בין הקבצים כדי לשמור על ניווט קל, התאמה לעתיד ויכולת тестирование. הנה מבנה מומלץ לפרויקט גדול:

my_fastapi_project/
├── app/
│   ├── __init__.py
│   ├── main.py              # נקודת הכניסה
│   ├── core/                # הגדרות כלליות
│   │   ├── config.py         # משתנים סביבתיים
│   │   └── security.py       # אימות ואבטחה
│   ├── models/              # מודלים של Pydantic
│   │   ├── user.py
│   │   └── item.py
│   ├── schemas/             # סכמות לקלט/פלט (לרוב זהה ל-Models, אבל אפשר להרחיב)
│   ├── api/                 # מסילות (routes)
│   │   ├── __init__.py
│   │   ├── v1/
│   │   │   ├── __init__.py
│   │   │   ├── users.py
│   │   │   └── items.py
│   ├── db/                  # ניהול חיבור למסד נתונים
│   │   ├── base.py
│   │   └── session.py
│   └── dependencies.py      # תלויות משותפות (למשל: get_db)
├── alembic/                 # migraции ל-SQLAlchemy
├── tests/                   # בדיקות
├── requirements.txt
└── README.md
אזהרה חשובה: אל תשתמשו במבנה אחד-קובץ עבור פרויקטים גדולים. חוסר הארגון יוביל לקשיי תחזוקה, אי יכולת לכתוב בדיקות יעילות, וקושי בשילוב עם צוות. המבנה לעיל הוא הסטנדרט בתעשייה ומאושר על ידי מסמכי ה-FastAPI Official Docs.

אימות ומודלים עם Pydantic

אחד מהיתרונות הגדולים של FastAPI הוא השימוש האוטומטי ב-Pydantic לאימות ולתיעוד. כל מודל שמתאר נתוני כניסה או יציאה מוגדר כ-class שמכיל את השדות וההגבלות שלו.

# app/models/user.py
from pydantic import BaseModel, EmailStr, Field
from typing import Optional

class UserBase(BaseModel):
    email: EmailStr
    full_name: Optional[str] = None

class UserCreate(UserBase):
    password: str = Field(..., min_length=8)

class UserOut(UserBase):
    id: int
    is_active: bool

    class Config:
        orm_mode = True  # מאפשר להשתמש במודל עם SQLAlchemy ORM

כעת נוכל להשתמש בו ברoutes:

# app/api/v1/users.py
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.models.user import UserCreate, UserOut
from app.core.security import get_password_hash

router = APIRouter()

@router.post("/users/", response_model=UserOut, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    # בדיקה אם משתמש כבר קיים
    existing_user = db.query(User).filter(User.email == user.email).first()
    if existing_user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="משתמש עם כתובת אימייל זו כבר קיים"
        )
    
    # יצירת משתמש חדש
    hashed_password = get_password_hash(user.password)
    db_user = User(email=user.email, full_name=user.full_name, hashed_password=hashed_password)
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

המערכת תבצע אוטומטית:

חיבור למסד נתונים עם SQLAlchemy

לרוב הפרויקטים נזדקקים למסד נתונים. FastAPI עובדת מצוין עם SQLAlchemy (ORM) ובאופן אסינכרוני גם עם asyncpg או aiomysql. כאן נציג גישה סינכרונית — פשוטה יותר לתחילת הדרך.

# app/db/base.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.core.config import settings

engine = create_engine(
    settings.DATABASE_URL,
    pool_pre_ping=True,
    echo=settings.DEBUG  # רק בפיתוח
)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# app/db/session.py
from app.db.base import SessionLocal, Base
from app.db.base import engine

def init_db():
    Base.metadata.create_all(bind=engine)

# תלות לשימוש בכל route
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

הגדרת משתנים סביבתיים

ניצור קובץ .env בשרש הפרויקט:

DATABASE_URL=postgresql://user:password@localhost:5432/fastapi_db
DEBUG=True
SECRET_KEY=your-super-secret-key-change-in-production

והגדרת config.py:

# app/core/config.py
from pydantic import BaseSettings

class Settings(BaseSettings):
    DATABASE_URL: str
    DEBUG: bool = False
    SECRET_KEY: str

    class Config:
        env_file = ".env"

settings = Settings()

אבטחה ואימות משתמשים

אבטחה היא קריטית בכל API. FastAPI מספקת תמיכה מובנית באימות באמצעות JWT (JSON Web Tokens) ו-OAuth2PasswordBearer.

# app/core/security.py
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from app.core.config import settings
from app.db.session import get_db
from app.models.user import UserOut
from sqlalchemy.orm import Session

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
    return pwd_context.hash(password)

def create_access_token(data: dict, expires_delta: timedelta = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(hours=24)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm="HS256")
    return encoded_jwt

ועכשיו נוסיף נקודת כניסה לאימות:

# ב-main.py או ב-api/v1/auth.py
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.core.security import authenticate_user, create_access_token
from app.models.user import UserOut

router = APIRouter()

@router.post("/token", response_model=dict)
def login_for_access_token(
    form_data: OAuth2PasswordRequestForm = Depends(),
    db: Session = Depends(get_db)
):
    user = authenticate_user(db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="שם משתמש או סיסמה שגויים",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=30)
    access_token = create_access_token(
        data={"sub": user.email}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}
טיפ מקצועי: השתמשו תמיד ב-OAuth2PasswordRequestForm ולא בקבלת username ו-password כ-JSON. זה מתאים לסטנדרט OAuth2, מתועד אוטומטית, ומאפשר ללקוחות להשתמש בכלי כמו Postman או Swagger ללא בעיות.

תהליך triểnת פרודקשן: Docker + NGINX + Uvicorn

לפני שה-API עולה לפרודקשן, יש לוודא שהיא מוגנת, מסונכרנת, ויכולה להתמודד עם עומסים. הנה תהליך triểnת מלא ומוכח.

1. קובץ Dockerfile

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.main:app", "--host", "0.0.0.0:8000", "--port", "8000", "--workers", "4", "--reload"]

2. קובץ docker-compose.yml

version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/fastapi_db
      - SECRET_KEY=${SECRET_KEY}
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:15
    environment:
      - POSTGRES_DB=fastapi_db
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data/

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - web

volumes:
  postgres_data:

3. קובץ nginx.conf פשוט

events {
    worker_connections 1024;
}

http {
    upstream fastapi_app {
        server web:8000;
    }

    server {
        listen 80;
        server_name your-domain.com;

        location / {
            proxy_pass http://fastapi_app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        location /docs {
            proxy_pass http://fastapi_app/docs;
        }

        location /redoc {
            proxy_pass http://fastapi_app/redoc;
        }
    }
}

השוואה: סביבת פיתוח לעומת פרודקשן

תכונה סביבת פיתוח סביבת פרודקשן
שרת Uvicorn עם --reload Uvicorn עם מספר workers, ללא reload
בסיס נתונים SQLite או PostgreSQL מקומי PostgreSQL עם עותקים, backup, connection pooling
אבטחה HTTPS לא נדרש, סיסמאות פשוטות HTTPS חובה, JWT עם זמן תפוגה קצר, rate limiting
תיעוד פעיל ב-/docs ו-/redoc מושבת או מוגבל לגישה פנימית בלבד
ניטור אין או Consul/Loguru בסיסי Prometheus + Grafana, Sentry, ELK Stack
הערה אחרונה: בפרודקשן, לעולם אל תחשפו את /docs לציבור הרחב. ניתן להשבית אותו לחלוטין על ידי הגדרת docs_url=None ב-FastAPI(), או לשלוט בגישה באמצעות NGINX או middlware. זה מפחית סיכונים של סריקות אוטומטיות וחשיפות מבנה הפנים של ה-API.