رفتن به محتوا

چرا باید خروجی خطاهای API در لاراول را همیشه JSON کنید؟ بررسی expectsJson و shouldRenderJsonWhen

یکی از رفتارهای کمتر شناخته‌شده‌ی لاراول این است که حتی در مسیرهای API نیز تضمینی وجود ندارد که پاسخ خطا همیشه به‌صورت JSON برگردانده شود.

بسیاری از توسعه‌دهندگان تصور می‌کنند هر درخواستی که به مسیرهای api/* ارسال شود، در صورت وقوع خطا، پاسخ JSON دریافت خواهد کرد. اما واقعیت این است که لاراول صرفاً بر اساس مسیر درخواست تصمیم نمی‌گیرد؛ بلکه ابتدا تلاش می‌کند تشخیص دهد کلاینت چه نوع پاسخی انتظار دارد. اگر این تشخیص به این نتیجه برسد که کلاینت انتظار JSON ندارد، حتی برای مسیرهای API نیز ممکن است یک صفحه HTML به‌عنوان پاسخ خطا تولید شود. این موضوع می‌تواند باعث بروز مشکلات متعددی در Front-end، اپلیکیشن‌های موبایل و سایر مصرف‌کنندگان API شود.

در این مقاله بررسی می‌کنیم:

  • چرا این اتفاق رخ می‌دهد؟
  • نقش expectsJson() و wantsJson() چیست؟
  • چرا shouldRenderJsonWhen() بهترین راه‌حل این مشکل است؟
  • و این قابلیت چه مزیتی نسبت به راهکارهای قدیمی دارد؟

چرا لاراول همیشه JSON برنمی‌گرداند؟

لاراول برای تشخیص نوع پاسخ از متدهای مختلفی استفاده می‌کند. مهم‌ترین متدی که در فرآیند مدیریت Exceptionها نقش دارد $request->expectsJson() است. این متد بررسی می‌کند که آیا کلاینت انتظار دریافت JSON را دارد یا خیر. اگر نتیجه این بررسی false باشد، Exception Handler مسیر رندر HTML را انتخاب می‌کند.

در نتیجه ممکن است برای درخواست زیر:

GET /api/users

در صورت وقوع یک Exception، خروجی چیزی شبیه صفحه پیش‌فرض خطای لاراول باشد، نه یک پاسخ JSON.

expectsJson() چگونه کار می‌کند؟

متد expectsJson() تنها به مسیر درخواست نگاه نمی‌کند. این متد عوامل مختلفی را بررسی می‌کند، از جمله:

  • وجود هدر Accept: application/json
  • درخواست‌های AJAX که هدر X-Requested-With: XMLHttpRequest را ارسال می‌کنند.
  • سایر شرایط داخلی فریم‌ورک که نشان می‌دهند کلاینت احتمالاً انتظار دریافت JSON دارد.

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

تفاوت expectsJson() و wantsJson()

اگر سورس لاراول را بررسی کنید، متوجه می‌شوید که این دو متد مسئولیت‌های متفاوتی دارند.

wantsJson()

این متد تنها هدر Accept را بررسی می‌کند. اگر مقدار آن شامل application/json باشد، مقدار true بازگردانده می‌شود. به عبارت دیگر، این متد صرفاً ترجیح اعلام‌شده توسط کلاینت را بررسی می‌کند.

expectsJson()

این متد در سطح بالاتری قرار دارد. علاوه بر wantsJson()، شرایط دیگری مانند درخواست‌های AJAX را نیز در نظر می‌گیرد تا تشخیص دهد آیا کلاینت احتمالاً انتظار پاسخ JSON دارد یا خیر. به همین دلیل، در بسیاری از قسمت‌های داخلی لاراول از جمله Exception Handler، از expectsJson() استفاده می‌شود.

به صورت خلاصه:

متدوظیفه
wantsJson()بررسی هدر Accept
expectsJson()تشخیص کلی انتظار کلاینت برای دریافت JSON

چرا این رفتار برای API مناسب نیست؟

در معماری‌های امروزی، API معمولاً توسط چندین کلاینت مصرف می‌شود.

از جمله:

  • React
  • Vue
  • Flutter
  • React Native
  • Android
  • iOS
  • سایر Backendها
  • Microserviceها

تمام این کلاینت‌ها انتظار دارند پاسخ‌ها ساختاری مشخص داشته باشند.

مثلاً:

{
    "message": "Unauthenticated."
}

اما اگر به‌جای JSON یک صفحه HTML دریافت شود:

  • Parser سمت کلاینت دچار خطا می‌شود.
  • مدیریت خطاها از کار می‌افتد.
  • Debug کردن سخت‌تر می‌شود.
  • رفتار API غیرقابل پیش‌بینی خواهد بود.

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

راهکار استاندارد لاراول

از لاراول 11 به بعد، متدی به نام shouldRenderJsonWhen() معرفی شده است.

در فایل bootstrap/app.php می‌توانید بنویسید:

->withExceptions(function ($exceptions) {
    $exceptions->shouldRenderJsonWhen(
        fn ($request) => $request->is('api/*')
    );
})

این کد به Exception Handler اعلام می‌کند:

هر Exception مربوط به مسیرهای api/* باید بدون توجه به نتیجه expectsJson() به‌صورت JSON رندر شود.

shouldRenderJsonWhen() دقیقاً چه چیزی را تغییر می‌دهد؟

نکته مهم این است که این متد:

  • هدرهای درخواست را تغییر نمی‌دهد.
  • رفتار expectsJson() را تغییر نمی‌دهد.
  • منطق wantsJson() را Override نمی‌کند.

بلکه فقط در مرحله Exception Rendering تصمیم می‌گیرد که خروجی نهایی چه فرمتی داشته باشد. به همین دلیل این روش، معماری تمیزتری نسبت به اجبار کردن هدر Accept در Middleware دارد.

مقایسه با راهکارهای قدیمی

قبل از معرفی shouldRenderJsonWhen()، معمولاً یکی از دو روش زیر استفاده می‌شد.

Override کردن متد render()

در نسخه‌های قدیمی لاراول، توسعه‌دهندگان منطق مربوط به API را داخل کلاس App\Exceptions\Handler پیاده‌سازی می‌کردند. این روش اگرچه کار می‌کرد، اما باعث شلوغ شدن Exception Handler می‌شد.

استفاده از Middleware

برخی پروژه‌ها هدر Accept: application/json را به تمام درخواست‌های API اضافه می‌کردند. این راهکار نیز مؤثر است، اما مشکل را از سمت Request حل می‌کند، نه از سمت Exception Rendering. در مقابل، shouldRenderJsonWhen() دقیقاً در همان لایه‌ای قرار دارد که مسئول تولید پاسخ خطاست و از نظر معماری، انتخاب مناسب‌تری محسوب می‌شود.

مزایای استفاده از shouldRenderJsonWhen()

استفاده از این قابلیت مزایای متعددی دارد.

  • تمام خطاهای API همیشه JSON خواهند بود.
  • رفتار API دیگر به هدر Accept وابسته نیست.
  • Front-end همیشه پاسخ قابل پیش‌بینی دریافت می‌کند.
  • مدیریت Exceptionها ساده‌تر می‌شود.
  • نگهداری پروژه در بلندمدت آسان‌تر خواهد بود.
  • ساختار پاسخ‌ها در تمام سرویس‌ها یکپارچه باقی می‌ماند.

چه زمانی از این قابلیت استفاده کنیم؟

اگر پروژه شما دارای API است که توسط اپلیکیشن موبایل، SPA، Microservice یا سرویس‌های شخص ثالث مصرف می‌شود، تقریباً همیشه بهتر است این تنظیم را فعال کنید. البته اگر برخی Endpointها باید بسته به نوع کلاینت هم HTML و هم JSON تولید کنند، می‌توانید شرط داخل shouldRenderJsonWhen() را متناسب با نیاز پروژه تغییر دهید.

قطعه کد مربوط به bootstrap/app.php
اسکرین‌شات یا قطعه کد مربوط به bootstrap/app.php

جمع‌بندی

رفتار پیش‌فرض لاراول بر پایه تشخیص انتظار کلاینت طراحی شده است و به همین دلیل از متد expectsJson() برای انتخاب نوع پاسخ استفاده می‌کند. این رفتار در بسیاری از سناریوها منطقی است، اما در APIهای مدرن که خروجی آن‌ها باید همیشه ساختاری ثابت داشته باشد، وابسته بودن فرمت پاسخ به هدر Accept می‌تواند باعث بروز رفتارهای غیرمنتظره شود.

متد shouldRenderJsonWhen() که در لاراول 11 معرفی شده، راهکاری استاندارد و تمیز برای حل این مشکل است. این متد بدون تغییر در هدرهای درخواست یا منطق expectsJson()، در مرحله رندر شدن Exceptionها تعیین می‌کند که پاسخ نهایی برای مسیرهای API همیشه به‌صورت JSON تولید شود.

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

منتشر شده در آموزش‌های من

اولین باشید که نظر می دهید

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *