اولین باری که یک REST API را برای یک پروژه فروشگاهی طراحی کردم، فکر می‌کردم چون endpointها پاسخ می‌دهند و JSON برمی‌گردانند، همه‌چیز درست است. سه ماه بعد، وقتی تیم اپلیکیشن موبایل از ناسازگاری پاسخ‌ها شکایت کرد و تیم امنیت یک حفره در احراز هویت را گزارش داد، فهمیدم طراحی API فقط نوشتن چند route نیست؛ یک قرارداد بلندمدت با مصرف‌کنندگان است. اگر تازه با مفاهیم پایه آشنا می‌شوید، پیشنهاد می‌کنم ابتدا REST API چیست را بخوانید؛ این مقاله فرض می‌کند می‌دانید REST چیست و می‌خواهد اشتباهاتی را باز کند که در عمل، پروژه‌ها را به بدهی فنی تبدیل می‌کنند.

چرا اشتباهات REST API گران تمام می‌شود؟

API یک قرارداد است. وقتی این قرارداد را می‌شکنید، هزینه‌اش را در سه لایه می‌پردازید: لایه مصرف‌کنندگان، لایه تیم داخلی، و لایه کسب‌وکار. در لایه اول، کلاینت‌های موبایل، وب و شرکای تجاری که روی endpointهای شما ساخته‌اند، با تغییرات ناگهانی می‌شکنند. در لایه دوم، تیم پشتیبانی فنی باید ساعات طولانی صرف پاسخ به تیکت‌های ناسازگاری کند. و در لایه سوم، اعتماد بازار به پلتفرم شما لطمه می‌خورد.

اما گران‌ترین اشتباه، آن دسته‌ای است که در ابتدا پنهان می‌ماند. یک endpoint بدون احراز هویت مناسب، ممکن است ماه‌ها در محیط توسعه کار کند و کسی متوجه نشود؛ تا روزی که یک مهاجم آن را کشف کند. یک پاسخ بدون صفحه‌بندی، ممکن است با هزار رکورد آزمایشی سریع باشد؛ تا روزی که پایگاه داده به میلیون رکورد برسد و سرور زیر بار از پا دربیاید. اگر هنوز درک دقیقی از جایگاه API در معماری نرم‌افزار ندارید، مطالعه API چیست و چه کاربردهایی دارد می‌تواند تصویر بزرگ‌تر را روشن کند.

در ادامه، پانزده اشتباه رایج را با ترتیب از طراحی تا عملیات مرور می‌کنم. برای هر اشتباه، ابتدا نشانه‌های آن را می‌گویم، بعد ریشه فنی‌اش را باز می‌کنم، و در نهایت راهکار عملی و قابل اجرا ارائه می‌دهم.

اشتباه اول: طراحی URL بدون اصول REST

اولین جایی که اشتباهات خودشان را نشان می‌دهند، ساختار URL است. رایج‌ترین خطا، قرار دادن فعل در مسیر است. مثلاً GET /getUser/123 یا POST /createOrder. در معماری REST، URL باید یک منبع (resource) را شناسایی کند، نه یک عمل. درست این است: GET /users/123 و POST /orders. فعل در متد HTTP نهفته است، نه در مسیر.

خطای دوم، استفاده از اسامی جمع و مفرد به شکل ناهماهنگ است. یک API که هم /user دارد و هم /orders، برای مصرف‌کننده گیج‌کننده است. قاعده ساده: همیشه جمع استفاده کنید. /users، /orders، /products. اگر قرار است یک منبع را در مسیر تودرتو کنید، سلسله‌مراتب را منطقی نگه دارید: GET /users/123/orders یعنی سفارش‌های کاربر ۱۲۳. اما از تودرتویی بیش از دو سطح پرهیز کنید؛ GET /users/123/orders/456/items/789/reviews نه‌فقط خواندنش سخت است، بلکه نگهداری‌اش هم کابوس می‌شود.

خطای سوم، استفاده از query string برای شناسایی منبع اصلی است. GET /api?action=getUser&id=123 یک الگوی قدیمی است که در APIهای RPC دیده می‌شود، نه REST. query string فقط برای فیلتر، مرتب‌سازی، صفحه‌بندی و جستجو استفاده می‌شود: GET /users?role=admin&sort=name.

خطای چهارم، بی‌توجهی به حساسیت حروف است. برخی سرورها /Users و /users را یکی می‌بینند و برخی دیگر نه. برای جلوگیری از رفتار غیرقابل پیش‌بینی، همیشه از حروف کوچک استفاده کنید. همچنین از کاراکترهای خاص، فاصله و علامت‌های غیرضروری در URL پرهیز کنید. اگر اصول طراحی REST را جدی می‌گیرید، مطالعه اصول طراحی REST API را پیشنهاد می‌کنم؛ چارچوبی که در ادامه همین مقاله به آن ارجاع می‌دهم.

URL در REST یک جمله است: منبع را معرفی می‌کند، نه عمل را. اگر فعل در URL دارید، یعنی هنوز با REST غریبه‌اید.

اشتباه دوم: استفاده نادرست از متدهای HTTP

متدهای HTTP قلب معنایی REST هستند. GET برای خواندن، POST برای ایجاد، PUT برای جایگزینی کامل، PATCH برای به‌روزرسانی جزئی، و DELETE برای حذف. رایج‌ترین اشتباه این است که همه‌چیز با POST انجام شود. POST /updateUser یا POST /deleteProduct نشانه APIای است که به REST وفادار نمانده.

اشتباه دوم، استفاده از GET برای عملیات تغییردهنده است. GET /users/123/delete نه‌فقط معنای REST را نقض می‌کند، بلکه خطر جدی امنیتی دارد: خزنده‌های وب، پیش‌بارگذارهای مرورگر و ابزارهای کش می‌توانند به‌طور تصادفی این درخواست را ارسال کنند. همچنین GET باید بدون side effect باشد و پاسخش قابل کش باشد.

اشتباه سوم، تمایز ندادن بین PUT و PATCH است. PUT یعنی جایگزینی کامل منبع؛ اگر فیلدی را در payload نفرستید، آن فیلد حذف یا به مقدار پیش‌فرض برمی‌گردد. PATCH یعنی فقط فیلدهای ارسالی به‌روزرسانی می‌شوند. استفاده از PUT برای به‌روزرسانی جزئی، می‌تواند داده‌های کاربر را ناخواسته پاک کند.

اشتباه چهارم، ارسال body در DELETE است. برخی سرورها و پروکسی‌ها body در DELETE را نادیده می‌گیرند و برخی دیگر آن را رد می‌کنند. اگر نیاز به حذف گروهی دارید، از یک endpoint اختصاصی با POST استفاده کنید یا شناسه‌ها را در query string بفرستید.

اشتباه پنجم، بی‌توجهی به idempotency است. GET، PUT و DELETE باید idempotent باشند؛ یعنی ارسال چندباره یک درخواست، همان نتیجه را بدهد. اگر PUT /users/123 را دو بار بفرستید، باید همان وضعیت قبلی حفظ شود، نه اینکه دو بار کاربر ایجاد شود. این موضوع در REST یک اصل بنیادین است که بسیاری از توسعه‌دهندگان آن را نادیده می‌گیرند.

اشتباه سوم: کدهای وضعیت نادرست یا ناسازگار

کدهای وضعیت HTTP زبان مشترک سرور و کلاینت هستند. وقتی این زبان را اشتباه به کار می‌برید، کلاینت نمی‌داند چه اتفاقی افتاده و نمی‌تواند واکنش درست نشان دهد.

رایج‌ترین اشتباه، برگرداندن 200 OK برای خطاهاست. بدنه پاسخ می‌گوید {"error": "user not found"} اما کد وضعیت ۲۰۰ است. این کار منطق مدیریت خطا در کلاینت را غیرقابل اعتماد می‌کند، چون کلاینت باید محتوای بدنه را parse کند تا بفهمد موفقیت یا شکست رخ داده. کد وضعیت باید مستقل از بدنه، وضعیت را برساند.

اشتباه دوم، اشتباه گرفتن 400 Bad Request و 422 Unprocessable Entity است. 400 یعنی درخواست از نظر ساختاری نامعتبر است؛ JSON ناقص، فیلد اجباری غایب، نوع داده اشتباه. 422 یعنی ساختار درست است اما اعتبارسنجی منطقی شکست خورده؛ مثلاً ایمیل تکراری است یا تاریخ پایان قبل از تاریخ شروع است.

اشتباه سوم، تمایز ندادن 401 Unauthorized و 403 Forbidden است. 401 یعنی شما احراز هویت نشده‌اید؛ توکن ندارید یا توکن منقضی شده. 403 یعنی احراز هویت شده‌اید اما اجازه دسترسی به این منبع را ندارید. ارسال ۴۰۱ برای کاربری که لاگین کرده اما دسترسی ندارد، کلاینت را به سمت اشتباه می‌فرستد و ممکن است باعث حلقه بی‌پایان ورود شود.

اشتباه چهارم، استفاده از 404 برای خطاهای اعتبارسنجی است. اگر GET /users/999 انجام می‌دهید و کاربر ۹۹۹ وجود ندارد، 404 درست است. اما اگر POST /users با ایمیل نامعتبر انجام می‌دهید، 404 بی‌معنی است؛ اینجا 400 یا 422 مناسب است.

اشتباه پنجم، استفاده از 500 Internal Server Error برای خطاهای کلاینت است. اگر کلاینت فیلد اجباری را نفرستاده، سرور سالم است و مشکل از درخواست است. 500 فقط برای خطاهای غیرمنتظره سرور استفاده می‌شود. در نهایت، ثبات را رعایت کنید: یک خطا در همه endpointها باید یک کد وضعیت و یک ساختار بدنه داشته باشد.

اشتباه چهارم: احراز هویت و مجوزدهی ضعیف

احراز هویت (Authentication) یعنی تشخیص هویت مصرف‌کننده. مجوزدهی (Authorization) یعنی تعیین اینکه او چه کاری می‌تواند انجام دهد. اشتباه در هر کدام، API را به یک در باز تبدیل می‌کند.

اولین اشتباه، قرار دادن API key در URL است. GET /users?api_key=abc123 خطر جدی دارد: URLها در لاگ سرور، تاریخچه مرورگر، هدر Referer و حتی کش پروکسی‌ها ثبت می‌شوند. API key باید در هدر Authorization ارسال شود: Authorization: Bearer abc123.

اشتباه دوم، نداشتن انقضا برای توکن‌هاست. توکنی که هرگز منقضی نمی‌شود، در صورت سرقت، برای همیشه معتبر است. توکن‌های دسترسی باید عمر کوتاه داشته باشند (۱۵ دقیقه تا چند ساعت) و توکن‌های تازه‌سازی (Refresh Token) عمر بلندتر اما یک‌بارمصرف و قابل ابطال باشند.

اشتباه سوم، نادیده گرفتن scope و نقش‌هاست. توکنی که فقط برای خواندن صادر شده، نباید بتواند منابع را تغییر دهد. اگر سیستم شما نقش‌ها را در سطح endpoint چک نمی‌کند، یک توکن دسترسی محدود می‌تواند به ابزاری برای تخریب تبدیل شود.

اشتباه چهارم، اعتماد به JWT بدون بررسی امضاست. برخی توسعه‌دهندگان payload را decode می‌کنند و به محتوای آن اعتماد می‌کنند، بدون اینکه امضای توکن را با کلید عمومی یا مخفی سرور بررسی کنند. این کار به مهاجم اجازه می‌دهد هر ادعایی را در توکن جعل کند.

اشتباه پنجم، نگهداری secretها در کد یا مخزن عمومی است. کلیدهای امضای JWT، رمزهای دیتابیس و API keyهای سرویس‌های خارجی باید در متغیرهای محیطی یا سیستم مدیریت راز نگهداری شوند. اگر این مفاهیم برایتان تازه است، مطالعه راهنمای احراز هویت در REST API مسیر کامل‌تری ارائه می‌دهد.

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

اشتباه پنجم: نداشتن استراتژی نسخه‌بندی

هر API زنده، در طول زمان تغییر می‌کند. اگر استراتژی نسخه‌بندی نداشته باشید، اولین تغییر شکننده، کلاینت‌های قدیمی را از کار می‌اندازد. سه رویکرد اصلی وجود دارد: نسخه در URL (/v1/users)، نسخه در هدر (Accept: application/vnd.api.v1+json) و نسخه در query string (/users?version=1). رویکرد URL ساده‌ترین و قابل‌مشاهده‌ترین است و برای اکثر پروژه‌ها توصیه می‌شود.

اشتباه رایج، ارتقای نسخه بدون برنامه است. وقتی /v1 را به /v2 می‌برید، باید سیاست deprecation داشته باشید: اطلاع‌رسانی از قبل، دوره همزیستی، مستندسازی تفاوت‌ها و در نهایت حذف. اگر کلاینت‌ها را غافلگیر کنید، اعتماد را از دست می‌دهید.

اشتباه دوم، نگهداری بی‌پایان نسخه‌های قدیمی است. هر نسخه‌ای که زنده نگه می‌دارید، هزینه نگهداری، تست و امنیت دارد. یک سیاست واقع‌بینانه: حداکثر دو نسخه فعال در یک زمان، با دوره deprecation شش تا دوازده ماهه. اگر درباره جزئیات فنی این تصمیم مطمئن نیستید، راهنمای نسخه‌بندی REST API را مطالعه کنید.

اشتباه ششم: مدیریت خطاهای مبهم

خطاها بخشی از تجربه API هستند. وقتی خطایی رخ می‌دهد، کلاینت باید بداند چه اتفاقی افتاده، چرا، و چگونه می‌تواند رفع کند. بدترین کار این است که پاسخ خطا فقط بگوید {"error": "Something went wrong"}.

یک ساختار خطای خوب شامل این اجزاست: کد وضعیت HTTP، یک کد خطای ماشین‌خوان (مثل USER_NOT_FOUND)، پیام انسانی قابل فهم، و در صورت لزوم، جزئیات فیلد به فیلد برای خطاهای اعتبارسنجی. نمونه:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "One or more fields are invalid.",
    "details": [
      { "field": "email", "code": "INVALID_FORMAT", "message": "Email format is invalid." },
      { "field": "age", "code": "OUT_OF_RANGE", "message": "Age must be between 18 and 120." }
    ]
  }
}

اشتباه دوم، برگرداندن stack trace در محیط production است. این کار اطلاعات حساس درباره ساختار داخلی، نام فایل‌ها و نسخه کتابخانه‌ها را لو می‌دهد. stack trace فقط در محیط توسعه یا لاگ داخلی سرور جای دارد.

اشتباه سوم، نداشتن correlation ID است. وقتی خطایی در production رخ می‌دهد، باید بتوانید درخواست را در لاگ‌ها ردیابی کنید. یک شناسه یکتا در هدر X-Request-ID که در پاسخ هم برگردانده می‌شود، این کار را ممکن می‌کند. برای آشنایی با ساختار داده‌ای پاسخ‌ها و اهمیت JSON، مطلب JSON چیست و چگونه داده‌ها را ساختاردهی می‌کند را ببینید.

اشتباه هفتم: نادیده گرفتن صفحه‌بندی و فیلتر

وقتی GET /users را طراحی می‌کنید، اولین سؤال این نیست که چه فیلدهایی برگردانده شود؛ سؤال این است که اگر جدول یک میلیون رکورد داشت، چه اتفاقی می‌افتد؟ اگر پاسخ این است که همه رکوردها برگردانده می‌شوند، API شما یک بمب ساعتی است.

دو استراتژی صفحه‌بندی رایج وجود دارد: offset-based (?page=2&limit=50) و cursor-based (?cursor=abc&limit=50). صفحه‌بندی offset ساده‌تر است اما در مجموعه‌های بزرگ و متغیر، می‌تواند رکورد تکراری یا جاافتاده برگرداند. صفحه‌بندی cursor پایدارتر است و برای فیدهای زمانی و حجم بالا توصیه می‌شود.

اشتباه دوم، نداشتن metadata صفحه‌بندی در پاسخ است. کلاینت باید بداند چند رکورد در کل وجود دارد (یا حداقل آیا صفحه بعدی هست یا نه)، limit فعلی چقدر است، و لینک‌های next/prev کجاست. اگر total count محاسبه‌اش گران است، می‌توانید از آن صرف‌نظر کنید اما وجود has_more یا لینک next ضروری است.

اشتباه سوم، نداشتن فیلتر و مرتب‌سازی استاندارد است. اگر کلاینت مجبور باشد همه داده را بگیرد و در سمت خودش فیلتر کند، هم پهنای باند هدر می‌رود و هم بار سرور بالا می‌رود. فیلترهای رایج باید در API پشتیبانی شوند: ?status=active&created_after=2025-01-01&sort=-created_at.

اشتباه هشتم: گلوگاه‌های عملکردی

API کند، API شکست‌خورده است. حتی اگر همه‌چیز درست کار کند، اگر پاسخ‌دهی طول بکشد، مصرف‌کننده به سراغ راه‌حل دیگری می‌رود. چند اشتباه عملکردی تقریباً در همه پروژه‌ها دیده می‌شود.

اولین اشتباه، مسئله N+1 query است. در یک endpoint که لیست سفارش‌ها را برمی‌گرداند، برای هر سفارش یک کوئری جداگانه برای گرفتن نام مشتری زده می‌شود. اگر ۱۰۰ سفارش وجود داشته باشد، ۱۰۱ کوئری اجرا می‌شود. راه‌حل، استفاده از eager loading یا join است تا همه داده در یک یا دو کوئری گرفته شود.

اشتباه دوم، نداشتن کش است. پاسخ‌های قابل کش باید هدرهای Cache-Control و ETag داشته باشند. داده‌هایی که به‌ندرت تغییر می‌کنند (مثل لیست کشورها یا دسته‌بندی‌ها) می‌توانند در لایه‌های مختلف کش شوند: مرورگر، CDN، Redis، یا حافظه سرور.

اشتباه سوم، انجام عملیات سنگین به‌صورت همزمان است. اگر یک درخواست نیاز به ارسال ایمیل، تولید PDF یا همگام‌سازی با سرویس خارجی دارد، این کارها نباید در چرخه درخواست-پاسخ انجام شوند. باید در صف قرار بگیرند و پاسخ فوری با 202 Accepted برگردد.

اشتباه چهارم، نداشتن compression است. پاسخ‌های JSON می‌توانند به‌طور چشمگیری با gzip یا brotli فشرده شوند. برای پاسخ‌های بزرگ، این کار مصرف پهنای باند را تا ۸۰ درصد کاهش می‌دهد. اشتباه پنجم، over-fetching است؛ برگرداندن فیلدهایی که کلاینت نیاز ندارد. اگر کلاینت فقط به id و name نیاز دارد، چرا کل شیء را برگردانیم؟ راهکارهایی مثل فیلد selection (?fields=id,name) یا GraphQL این مشکل را حل می‌کنند. اگر به دنبال بهینه‌سازی جدی هستید، بهینه‌سازی عملکرد REST API را مطالعه کنید.

سرعت API، ویژگی لوکس نیست؛ بخشی از قرارداد است. اگر کندی را تحویل بدهید، مصرف‌کننده آن را به‌عنوان باگ می‌بیند.

اشتباه نهم: مستندسازی ضعیف

مستندسازی، پل بین API و مصرف‌کننده است. اگر مستندات نداشته باشید یا ناقص باشد، هر مصرف‌کننده جدید باید با آزمون و خطا endpointها را کشف کند. این کار زمان، انرژی و اعتماد را هدر می‌دهد.

استاندارد امروز، OpenAPI Specification (که قبلاً Swagger نامیده می‌شد) است. با OpenAPI، می‌توانید endpointها، پارامترها، پاسخ‌ها و مدل‌های داده را توصیف کنید. ابزارهایی مثل Swagger UI از همین specification، مستندات تعاملی می‌سازند که مصرف‌کننده می‌تواند درخواست واقعی بفرستد. برای آشنایی با این ابزار، مستندسازی REST API با Swagger را ببینید.

اشتباه رایج، به‌روز نکردن مستندات پس از تغییر کد است. اگر مستندات از کد جدا باشد، دیر یا زود از هم فاصله می‌گیرند. راه‌حل، تولید مستندات از کد یا specification است تا همیشه همگام بمانند. اشتباه دوم، نداشتن مثال است. توضیح انتزاعی کافی نیست؛ مصرف‌کننده باید یک نمونه درخواست و پاسخ واقعی ببیند. اشتباه سوم، مستند نکردن خطاهاست. فقط مسیر موفق کافی نیست؛ خطاهای ممکن، کدهای وضعیت و ساختار بدنه خطا هم باید مستند شوند. اشتباه چهارم، نداشتن changelog است. وقتی چیزی تغییر می‌کند، مصرف‌کننده باید بداند چه چیزی، کِی و چرا.

اشتباه دهم: تست ناکافی

API بدون تست، مثل پلی بدون بازرسی است. ممکن است روز اول سالم باشد، اما اولین تغییر جدی، آن را می‌شکند. اشتباه رایج، تست فقط مسیر موفق (happy path) است. باید سناریوهای خطا، ورودی‌های مرزی، payloadهای ناقص و درخواست‌های غیرمجاز هم تست شوند.

اشتباه دوم، نداشتن تست قرارداد (contract testing) است. وقتی کلاینت و سرور جداگانه توسعه می‌یابند، باید قراردادی وجود داشته باشد که هر دو طرف به آن متعهد باشند. ابزارهایی مثل Postman یا Pact می‌توانند این قرارداد را تست کنند. برای شروع با Postman، تست REST API با Postman را ببینید.

اشتباه سوم، نداشتن تست بار (load testing) است. باید بدانید API شما در ۱۰۰، ۱۰۰۰ یا ۱۰۰۰۰ درخواست همزمان چه رفتاری دارد. ابزارهایی مثل k6، Locust یا JMeter می‌توانند این سناریوها را شبیه‌سازی کنند. اشتباه چهارم، نداشتن تست امنیتی است. باید endpointها را در برابر injection، IDOR، mass assignment و دسترسی غیرمجاز تست کنید. بدون تست امنیتی، حفره‌ها تا روز حادثه پنهان می‌مانند.

اشتباه یازدهم: حفره‌های امنیتی

امنیت API، مجموعه‌ای از لایه‌هاست. یک اشتباه در هر لایه، کل سیستم را در معرض خطر قرار می‌دهد. رایج‌ترین حفره‌ها در APIها عبارت‌اند از injection، broken authentication، excessive data exposure، mass assignment و IDOR.

Injection در APIها فقط SQL نیست. NoSQL injection در پایگاه‌داده‌های document-based، command injection در سیستم‌های اجرای فرمان، و حتی LDAP injection هم وجود دارد. راه‌حل اصولی، اعتبارسنجی ورودی و استفاده از پارامترسازی (parameterized query) در همه لایه‌هاست. هرگز ورودی کاربر را مستقیم در کوئری، فرمان یا مسیر فایل قرار ندهید.

Mass assignment یعنی کلاینت بتواند فیلدهایی را تغییر دهد که نباید. مثلاً در PUT /users/123، اگر کلاینت فیلد role را بفرستد و سرور آن را بدون بررسی به‌روزرسانی کند، کاربر می‌تواند خودش را admin کند. راه‌حل، استفاده از DTO (Data Transfer Object) یا allowlist فیلدهای قابل‌تغییر است.

IDOR (Insecure Direct Object Reference) یعنی کاربر بتواند با تغییر یک شناسه، به منبعی که مالکش نیست دسترسی پیدا کند. GET /orders/456 باید بررسی کند که سفارش ۴۵۶ واقعاً متعلق به کاربر احراز هویت‌شده است یا نه. اگر فقط وجود سفارش را چک کنید، هر کاربری می‌تواند سفارش دیگران را ببیند. برای مطالعه عمیق‌تر این موضوعات، چگونه REST API امن بسازیم را توصیه می‌کنم.

اشتباه دوازدهم: نادیده گرفتن Rate Limiting

Rate limiting یعنی محدود کردن تعداد درخواست‌هایی که یک کلاینت می‌تواند در بازه زمانی مشخص ارسال کند. بدون آن، یک اسکریپت مخرب یا حتی یک باگ در کلاینت می‌تواند سرور شما را از پا دربیاورد.

اشتباه رایج، اعمال rate limit یکسان برای همه endpointهاست. endpointهای احراز هویت (login، register، forgot-password) باید سخت‌گیرانه‌تر محدود شوند تا جلوی brute force گرفته شود. endpointهای خواندن می‌توانند محدودیت نرم‌تر داشته باشند. endpointهای سنگین (مثل تولید گزارش) باید سهمیه جداگانه داشته باشند.

اشتباه دوم، نداشتن هدرهای اطلاع‌رسانی است. کلاینت باید بداند سهمیه‌اش چقدر است و چقدر باقی مانده. هدرهای استاندارد عبارت‌اند از X-RateLimit-Limit، X-RateLimit-Remaining و X-RateLimit-Reset. اشتباه سوم، نداشتن پاسخ درست هنگام عبور از حد است. باید 429 Too Many Requests با هدر Retry-After برگردانده شود تا کلاینت بداند کِی می‌تواند دوباره تلاش کند.

اشتباه سیزدهم: وابستگی به اسکیمای دیتابیس

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

راه‌حل، لایه DTO است. API باید یک مدل مستقل داشته باشد که فقط داده‌های موردنیاز را نشان می‌دهد. این لایه، هم تغییرات دیتابیس را از مصرف‌کننده پنهان می‌کند و هم اجازه می‌دهد فیلدهای حساس را حذف کنید. مثلاً مدل کاربر در دیتابیس ممکن است password_hash، internal_notes و stripe_customer_id داشته باشد، اما API فقط id، name و email را برمی‌گرداند.

اشتباه دوم، قفل شدن به ORM است. اگر API شما مستقیماً از متدهای ORM استفاده می‌کند و ساختار خروجی را به آن وابسته می‌کند، تغییر ORM یا بهینه‌سازی کوئری‌ها به بازنویسی API منجر می‌شود. لایه abstraction را جدی بگیرید. در این زمینه، مقایسه معماری REST و GraphQL می‌تواند دید بهتری بدهد؛ GraphQL یا REST را ببینید.

اشتباه چهاردهم: عدم مدیریت Content Negotiation

Content Negotiation یعنی سرور و کلاینت بر سر فرمت داده به توافق برسند. اکثر APIهای امروزی فقط JSON برمی‌گردانند و این تصمیم معقولی است. اما اشتباه اینجاست که هدر Accept کلاینت را نادیده بگیرید و حتی وقتی کلاینت Accept: application/xml می‌فرستد، JSON برگردانید.

رفتار درست این است که اگر فرمت درخواستی پشتیبانی نمی‌شود، 406 Not Acceptable برگردانید. این کار به کلاینت می‌گوید که انتظارش برآورده نشده و باید فرمت دیگری درخواست کند. همچنین هدر Content-Type پاسخ باید دقیقاً فرمت واقعی بدنه را مشخص کند.

اشتباه دوم، نداشتن نسخه در content type است. برخی APIها از Accept: application/vnd.myapi.v2+json استفاده می‌کنند که هم فرمت و هم نسخه را مشخص می‌کند. این رویکرد تمیز است اما پیاده‌سازی و مستندسازی‌اش پیچیده‌تر است. اگر آن را انتخاب می‌کنید، باید در همه endpointها یکدست اجرا شود.

اشتباه پانزدهم: نادیده گرفتن Idempotency

Idempotency یعنی ارسال چندباره یک درخواست، همان نتیجه را بدهد. این مفهوم در REST بنیادین است، اما در عمل زیاد نقض می‌شود. بزرگ‌ترین قربانی، عملیات پرداخت است: اگر کلاینت به دلیل timeout درخواست پرداخت را دوباره بفرستد و سرور idempotency را رعایت نکند، مشتری دو بار شارژ می‌شود.

راه‌حل استاندارد، هدر Idempotency-Key است. کلاینت یک شناسه یکتا برای هر عملیات ارسال می‌کند. سرور این شناسه را ذخیره می‌کند و اگر درخواست تکراری با همان کلید برسد، پاسخ قبلی را برمی‌گرداند بدون اینکه عملیات را دوباره اجرا کند. این الگو در APIهای پرداخت مثل Stripe بسیار رایج است.

اشتباه دوم، اعتماد به PUT برای idempotency است در حالی که پیاده‌سازی‌اش ناقص است. اگر PUT /users/123 را طوری بنویسید که هر بار یک رکورد جدید در جدول log ایجاد کند، idempotent نیست. باید اطمینان حاصل کنید که side effectهای جانبی هم idempotent هستند. در نهایت، اگر API شما برای وردپرس است، REST API در وردپرس نکات اختصاصی این پلتفرم را پوشش می‌دهد.

در API، تکرار یک درخواست نباید فاجعه بسازد. اگر می‌سازد، طراحی شما هنوز بالغ نشده است.

پرسش‌های پرتکرار درباره اشتباهات REST API

آیا استفاده از فعل در URL همیشه اشتباه است؟
در REST خالص، بله. URL باید منبع را معرفی کند و متد HTTP عمل را. اما در عمل، برخی عملیات‌ها مثل جستجوی پیچیده، محاسبه یا انتقال وجه، به‌سختی با مدل منبع-محور بیان می‌شوند. در این موارد، استفاده از یک endpoint اختصاصی با فعل قابل قبول است، به شرطی که آگاهانه و مستند باشد.

چه زمانی باید از PATCH و چه زمانی از PUT استفاده کنم؟
اگر کلاینت قصد دارد کل منبع را با نسخه جدید جایگزین کند، PUT. اگر فقط می‌خواهد چند فیلد را به‌روزرسانی کند، PATCH. PUT برای idempotency طبیعی‌تر است، اما PATCH می‌تواند حجم payload را کم کند. برای منابع بزرگ با فیلدهای زیاد، PATCH انتخاب بهتری است.

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

چطور بفهمم API من به اندازه کافی امن است؟
اول، چک‌لیست OWASP API Security Top 10 را مرور کنید. دوم، تست نفوذ انجام دهید، حداقل روی endpointهای حساس. سوم، لاگ‌های دسترسی را بررسی کنید تا الگوهای غیرعادی را ببینید. چهارم، سناریوهای حمله رایج (injection، IDOR، mass assignment) را روی API خودتان اجرا کنید. امنیت یک وضعیت نیست، یک فرآیند مداوم است.

آیا GraphQL جایگزین REST می‌شود؟
نه به‌طور کامل. GraphQL در سناریوهایی که کلاینت‌های متنوع با نیازهای متفاوت وجود دارد، مزایای جدی دارد. اما REST ساده‌تر، قابل‌کش‌تر و برای اکثر پروژه‌ها کافی است. انتخاب بین این دو، به نیاز پروژه بستگی دارد، نه به مد روز.

چطور بفهمم API من به اندازه کافی سریع است؟
سه شاخص کلیدی را اندازه بگیرید: latency (زمان پاسخ)، throughput (تعداد درخواست در ثانیه) و error rate. برای اکثر APIها، latency زیر ۲۰۰ میلی‌ثانیه برای endpointهای سبک و زیر ۱ ثانیه برای endpointهای سنگین قابل قبول است. اگر بالاتر است، ابتدا کوئری‌ها، سپس کش و در نهایت زیرساخت را بررسی کنید.

مسیر پیش رو: از REST API شکننده به API قابل اتکا

پانزده اشتباهی که مرور کردیم، الگوهای تکراری‌ای هستند که در پروژه‌های مختلف دیده‌ام. هیچ‌کدامشان غیرقابل پیشگیری نیستند، اما همه‌شان نیازمند تصمیم آگاهانه در زمان طراحی هستند. اگر در حال ساخت API جدیدی هستید، این فهرست را به‌عنوان چک‌لیست طراحی به کار ببرید. اگر API موجودی دارید، آن را به‌عنوان ابزار بازبینی استفاده کنید.

نکته کلیدی این است که API یک محصول زنده است، نه یک پروژه یک‌باره. هر تغییر، هر endpoint جدید و هر نسخه، باید با همان دقتی طراحی شود که بار اول. بهترین APIها آن‌هایی نیستند که بیشترین امکانات را دارند، بلکه آن‌هایی هستند که مصرف‌کننده می‌تواند بدون خواندن مستندات طولانی، حدس بزند چه رفتاری دارند. این پیش‌بینی‌پذیری، نتیجه رعایت همان اصولی است که در این مقاله مرور کردیم.

اگر تجربه‌ای از یک اشتباه REST API دارید که در این فهرست نبود، یا راه‌حل خلاقانه‌ای برای یکی از موارد پیدا کرده‌اید، خوشحال می‌شوم بشنوم. تجربه‌های واقعی از میدان، همیشه ارزشمندتر از تئوری هستند.