خیلی از باگ‌های امنیتی از جایی شروع میشن که API بیشتر از چیزی که واقعاً لازم بوده به کاربر برگردونده. مثلاً Endpoint اطلاعات یه کاربر رو از دیتابیس میگیره و بدون کنترل همون Object رو مستقیم برمیگردونه؛ چند هفته بعد مشخص میشه کنار نام و ایمیل، فیلدهایی مثل password_hash، شناسه‌های داخلی یا اطلاعات مدیریتی هم داخل Response قرار داشتن. برای جلوگیری از همین اتفاق، FastAPI اجازه میده از قبل مشخص کنیم خروجی هر Endpoint دقیقاً چه ساختاری داشته باشه و چه فیلدهایی اجازه خروج از API رو داشته باشن؛ به این ساختار مشخص برای پاسخ، Response Model گفته میشه.

Response Model در FastAPI چیست؟

وقتی یه Endpoint داده‌ای برمیگردونه، FastAPI میتونه اون داده رو قبل از ارسال به Client با یه مدل مشخص مقایسه کنه و فقط فیلدهایی رو نگه داره که داخل مدل خروجی تعریف شدن. این یعنی حتی اگه تابع شما Objectای با اطلاعات بیشتر برگردونه، لازم نیست تمام اون اطلاعات وارد JSON نهایی بشن. مستندات رسمی FastAPI هم تأکید میکنن که مدل پاسخ برای اعتبارسنجی، تبدیل داده، مستندسازی و مهم‌تر از همه محدود کردن فیلدهای خروجی استفاده میشه.

در واقع Response Model مثل یه فیلتر بین منطق داخلی برنامه و کاربر قرار میگیره؛ داخل سرور ممکنه اطلاعات زیادی داشته باشید، ولی فقط بخشی که برای Client لازمه از این فیلتر عبور میکنه.

چرا برگرداندن مستقیم اطلاعات می‌تواند خطرناک باشد؟

داده‌ای که داخل Back-end استفاده میشه الزاماً برای نمایش عمومی ساخته نشده. مدل User ممکنه علاوه بر نام و ایمیل شامل رمز پردازش‌شده، سطح دسترسی، وضعیت داخلی حساب یا اطلاعاتی باشه که فقط خود سرور باید ببینه. اگه کل Object بدون فیلتر برگرده، اضافه شدن یه فیلد جدید به مدل دیتابیس میتونه ناخواسته باعث اضافه شدن همون اطلاعات به Response هم بشه.

بهتره به جای اینکه هر بار موقع return یادتون باشه چه فیلدی رو حذف کنید، از اول یه قرارداد مشخص برای خروجی تعریف کنید. اینطوری حتی اگه Object داخلی اطلاعات بیشتری داشته باشه، FastAPI فقط داده‌هایی رو ارسال میکنه که مدل خروجی اجازه داده.

تفاوت مدل ورودی و مدل خروجی چیست؟

اطلاعاتی که کاربر برای انجام یه عملیات میفرسته معمولاً با چیزی که باید از API تحویل بگیره یکی نیست. مثلاً موقع ثبت‌نام، کاربر Username، Email و Password ارسال میکنه، ولی وقتی ثبت‌نام با موفقیت انجام شد منطقی نیست Password دوباره داخل Response قرار بگیره.

به مدلی که ساختار اطلاعات دریافتی رو مشخص میکنه میشه مدل ورودی گفت و مدلی که مشخص میکنه چه اطلاعاتی به Client برگرده مدل خروجی محسوب میشه. جدا کردن این دو باعث میشه مرز بین داده‌های داخلی و اطلاعات قابل نمایش خیلی واضح‌تر باشه.

استفاده از response_model در FastAPI

یه نمونه ساده برای ثبت کاربر میتونه این شکلی باشه:

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserCreate(BaseModel):
    username: str
    email: EmailStr
    password: str


class UserPublic(BaseModel):
    username: str
    email: EmailStr


@app.post("/users/", response_model=UserPublic)
async def create_user(user: UserCreate):
    saved_user = {
        "username": user.username,
        "email": user.email,
        "password": user.password,
        "internal_id": 1482
    }

    return saved_user

اینجا تابع create_user عمداً داده‌ای برمیگردونه که علاوه بر Username و Email شامل Password و یه شناسه داخلی هم هست، اما چون response_model=UserPublic تعریف شده، FastAPI خروجی رو بر اساس مدل UserPublic فیلتر میکنه و فقط فیلدهای مجاز وارد Response میشن. این دقیقاً یکی از کاربردهای اصلی Response Model در FastAPI هست.

البته در پروژه واقعی Password باید به شکل امن پردازش و ذخیره بشه؛ این مثال فقط برای نشون دادن تفاوت اطلاعات داخلی با خروجی API نوشته شده.

Response Model فقط برای امنیت نیست

جلوگیری از نمایش اطلاعات اضافه مهم‌ترین مزیتشه، ولی تنها کاربردش نیست. FastAPI با استفاده از مدل خروجی میتونه بررسی کنه داده‌ای که Endpoint برمیگردونه با قرارداد تعریف‌شده سازگار هست یا نه. اگه برنامه خروجی‌ای تولید کنه که با مدل اعلام‌شده تطابق نداشته باشه، این موضوع به عنوان مشکل سمت خود API در نظر گرفته میشه، نه خطای کاربر.

این ویژگی باعث میشه Client بتونه روی ساختار Response حساب کنه. وقتی اعلام کردید خروجی شامل username و email هست، فرانت‌اند نباید یه بار با این دو فیلد و بار دیگه با یه ساختار کاملاً متفاوت روبه‌رو بشه.

مستندات API هم دقیق‌تر می‌شوند

FastAPI از مدل پاسخ برای ساخت مستندات خودکار API هم استفاده میکنه. یعنی صفحه /docs میتونه نشون بده Response موفق Endpoint چه ساختاری داره و هر فیلد چه نوع داده‌ای داره. این اطلاعات در استاندارد توضیح ساختار API یعنی OpenAPI هم ثبت میشن.

این موضوع وقتی پروژه Front-end جدا داره یا چند نفر همزمان روی API کار میکنن خیلی مفیده، چون مدل خروجی عملاً تبدیل به یه قرارداد واضح بین Back-end و مصرف‌کننده API میشه.

آیا همیشه باید response_model بنویسیم؟

نه لزوماً. FastAPI میتونه از نوع خروجی تابع هم برای همین کار استفاده کنه، اما response_model زمانی خیلی کاربردیه که داده واقعی‌ای که داخل تابع برمیگردونید با چیزی که میخواید به Client نمایش داده بشه فرق داشته باشه. اگه هم نوع خروجی تابع و هم response_model تعریف بشن، FastAPI برای پاسخ از response_model استفاده میکنه.

برای پروژه‌های ساده شاید در ابتدا تفاوت زیادی حس نشه، ولی وقتی تعداد مدل‌ها و Endpointها بیشتر میشه، داشتن مدل خروجی مستقل باعث میشه کنترل داده‌ها خیلی راحت‌تر بشه.

فیلدهای اختیاری را هم می‌توان از خروجی حذف کرد

گاهی یه مدل فیلدهای اختیاری زیادی داره و نمیخواید مقدارهای None داخل Response دیده بشن. FastAPI برای چنین شرایطی گزینه‌هایی مثل response_model_exclude_none=True هم داره که میتونه فیلدهای بدون مقدار رو از خروجی نهایی حذف کنه. گزینه‌های مشابهی هم برای مقدارهای پیش‌فرض وجود دارن.

با این حال بهتره قبل از استفاده زیاد از گزینه‌های حذف و اضافه، مدل‌های خروجی رو درست طراحی کنید. اگه دو Endpoint واقعاً خروجی متفاوتی دارن، خیلی وقت‌ها ساخت دو مدل مشخص خواناتر از اینه که یه مدل بزرگ داشته باشید و در هر Endpoint نصف فیلدها رو دستی حذف کنید.

اشتباه رایج: یک مدل برای همه‌چیز

استفاده از یه مدل User برای ثبت‌نام، Login، پنل مدیریت و Response عمومی شاید اول پروژه ساده‌تر به نظر برسه، ولی خیلی زود باعث میشه اطلاعاتی وارد بخش‌هایی بشن که اصلاً بهشون نیاز ندارن. مدل ورودی ثبت‌نام ممکنه Password داشته باشه، مدل خروجی عمومی فقط نام و عکس بخواد و مدل مخصوص مدیر سیستم اطلاعات بیشتری نمایش بده.

تفکیک مدل‌ها کمی کد بیشتری ایجاد میکنه، ولی در عوض دقیقاً معلومه هر بخش چه داده‌ای دریافت و چه داده‌ای منتشر میکنه. این موضوع هم امنیت رو بهتر میکنه و هم تغییر دادن API در آینده رو ساده‌تر نگه میداره.

جمع‌بندی

Response Model در FastAPI کمک میکنه خروجی Endpointها فقط شامل اطلاعاتی باشه که واقعاً باید به Client نمایش داده بشن. به جای برگردوندن مستقیم Objectهای داخلی و امیدوار بودن به اینکه هیچ فیلد حساسی داخل Response قرار نگیره، میتونید یه مدل خروجی مشخص تعریف کنید و اجازه بدید FastAPI داده رو بر اساس همون مدل اعتبارسنجی، تبدیل و فیلتر کنه.

این روش مخصوصاً برای Userها، اطلاعات حساب، پنل‌های مدیریتی و هر داده‌ای که ترکیبی از اطلاعات عمومی و داخلی داره اهمیت زیادی پیدا میکنه. یه قانون ساده ارزش یادآوری داره: داده‌ای که داخل سرور وجود داره الزاماً نباید همون داده‌ای باشه که از API خارج میشه.