مقالات › معماری نرم‌افزار

اصول طراحی REST API؛ آدرس‌ها، متدها، کدهای وضعیت و نسخه‌بندی

APIهایی طراحی کنید که توسعه‌دهنده‌ها دوستشان دارند: نام‌گذاری منابع، متدهای HTTP، کدهای وضعیت، قالب خطا، صفحه‌بندی، فیلتر، نسخه‌بندی، Idempotency و امنیت.

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

به منبع فکر کنید، نه به عمل

REST سیستم شما را به‌صورت منبع (Resource) — یعنی اسم — مدل می‌کند که با مجموعه‌ی کوچکی از متدهای HTTP — یعنی فعل — تغییر می‌کنند.

❌ سبک RPC ✅ منبع‌محور
POST /getProducts GET /products
POST /createOrder POST /orders
GET /deleteUser?id=5 DELETE /users/5
POST /updateProductPrice PATCH /products/42

قوانین نام‌گذاری

  • از اسم جمع استفاده کنید: /products، /orders.
  • برای مسیرهای چندکلمه‌ای از kebab-case استفاده کنید: /order-items.
  • فقط برای مالکیت واقعی تودرتو کنید و بیش از یک سطح نه: /customers/7/orders.
  • آدرس‌ها را با حروف کوچک بنویسید و فعل در آن‌ها نگذارید، مگر برای عملیات واقعی غیر CRUD (POST /orders/42/cancel قابل قبول و واضح است).

متدهای HTTP را درست استفاده کنید

متد کاربرد Safe Idempotent
GET خواندن منبع یا مجموعه ✅ ✅
POST ساخت منبع / اجرای یک عملیات ❌ ❌
PUT جایگزینی کامل منبع ❌ ✅
PATCH به‌روزرسانی بخشی از منبع ❌ معمولاً ❌
DELETE حذف منبع ❌ ✅
  • Safe یعنی وضعیت را تغییر نمی‌دهد؛ GET هرگز نباید داده را تغییر دهد (خزنده‌ها و پیش‌بارگذارها آزادانه GET می‌زنند).
  • Idempotent یعنی تکرار درخواست همان اثر یک بار انجامش را دارد؛ وقتی شبکه درخواست را دوباره می‌فرستد مهم است.

کد وضعیت معنادار برگردانید

کد چه زمانی
200 OK GET / PUT / PATCH موفق همراه با بدنه
201 Created منبع ساخته شد — هدر Location را بفرستید
204 No Content موفقیت بدون بدنه (DELETE، برخی به‌روزرسانی‌ها)
400 Bad Request درخواست ناقص یا خطای اعتبارسنجی
401 Unauthorized احراز هویت نشده (توکن ندارد/نامعتبر)
403 Forbidden احراز هویت شده اما مجاز نیست
404 Not Found منبع وجود ندارد
409 Conflict تعارض وضعیت (ایمیل تکراری، عدم تطابق نسخه)
422 Unprocessable Entity ساختار درست اما از نظر معنایی نامعتبر (برخی تیم‌ها از 400 استفاده می‌کنند)
429 Too Many Requests عبور از محدودیت نرخ درخواست
500 Internal Server Error خطای غیرمنتظره‌ی سرور

هرگز 200 OK را همراه با { "success": false } برنگردانید؛ این کار ابزارهای HTTP، کش و مانیتورینگ را از کار می‌اندازد.

قالب یکسان برای خطا

از قالب استاندارد Problem Details (RFC 9457) استفاده کنید که ASP.NET Core به‌صورت پیش‌فرض پشتیبانی می‌کند:

{
  "type": "https://example.com/problems/validation",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "email": ["Email is required."],
    "price": ["Price must be greater than zero."]
  },
  "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00"
}

یک شکل خطای واحد در کل API یعنی کلاینت‌ها مدیریت خطا را فقط یک بار می‌نویسند. یک traceId اضافه کنید تا تیم پشتیبانی درخواست را در لاگ‌ها پیدا کند؛ اما در Production هرگز Stack Trace را لو ندهید.

صفحه‌بندی، فیلتر و مرتب‌سازی

مجموعه‌ها باید صفحه‌بندی شوند:

GET /products?page=2&pageSize=20
GET /products?category=laptops&minPrice=500&sort=-price,name
{
  "items": [ ... ],
  "page": 2,
  "pageSize": 20,
  "totalCount": 438
}
  • صفحه‌بندی Offset (page/pageSize) ساده است و پرش به صفحه‌ی دلخواه را پشتیبانی می‌کند.
  • صفحه‌بندی Cursor (?after=eyJpZCI6MTAwfQ) برای داده‌های بزرگ یا پرتغییر مثل فید، پایدار و سریع است.
  • در سرور سقف pageSize را اعمال کنید.
  • یک قرارداد رایج برای مرتب‌سازی نزولی: sort=-createdAt.

نسخه‌بندی

APIها تکامل پیدا می‌کنند و کلاینت‌ها فوراً به‌روز نمی‌شوند. از روز اول برای نسخه‌بندی برنامه داشته باشید:

GET /api/v1/orders/42        # نسخه در آدرس — ساده و آشکار
GET /api/orders/42
    api-version: 2.0         # نسخه در هدر — آدرس‌های تمیزتر

قوانینی که جلوی بیشتر تغییر نسخه‌ها را می‌گیرند:

  • تغییرات افزایشی امن‌اند: Endpoint جدید، فیلد اختیاری جدید.
  • تغییرات شکننده نسخه‌ی جدید لازم دارند: حذف یا تغییر نام فیلد، تغییر نوع یا معنا.
  • منسوخ شدن را با هدرهای Deprecation/Sunset و یک جدول زمانی اعلام کنید.

Idempotency برای POST

شبکه‌های موبایل درخواست را دوباره می‌فرستند. اگر درخواست «ایجاد پرداخت» Timeout بخورد و تکرار شود، ممکن است دو بار پول کسر شود. یک کلید Idempotency بپذیرید:

POST /payments
Idempotency-Key: 6f1c9c0e-8f4b-4c6e-9b4a-2f9b1d1e7a10

سرور کلید را همراه نتیجه ذخیره می‌کند؛ درخواست تکراری با همان کلید نتیجه‌ی اصلی را برمی‌گرداند، به‌جای اینکه دوباره اجرا شود.

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

  • DTO برگردانید، نه Entity دیتابیس.
  • نام‌گذاری یکدست داشته باشید (در JSON معمولاً camelCase).
  • تاریخ را با ISO 8601 و به UTC بفرستید: "2026-02-07T09:30:00Z".
  • مبالغ پولی را با رشته‌ی دسیمال یا عدد صحیح (کوچک‌ترین واحد) نمایش دهید؛ هرگز با Float.
  • از null آگاهانه استفاده کنید و معنای یک فیلد را بین نسخه‌ها عوض نکنید.

کش و هم‌زمانی

GET /products/42
ETag: "a3f9c2"
Cache-Control: public, max-age=60
  • کلاینت می‌تواند If-None-Match: "a3f9c2" بفرستد و 304 Not Modified بگیرد؛ صرفه‌جویی در پهنای باند.
  • در به‌روزرسانی، If-Match همراه با ETag جلوی گم شدن تغییرات را می‌گیرد: اگر کس دیگری منبع را تغییر داده باشد، 412 Precondition Failed برگردانید.

اصول امنیتی

  • HTTPS همه‌جا.
  • احراز هویت با توکن (JWT/OAuth2) و مجوزدهی برای همه‌ی Endpointها — مالکیت را بررسی کنید، نه فقط ورود را (جلوگیری از IDOR: سفارش /orders/43 نباید با توکن کاربر ۴۲ خوانده شود اگر مال او نیست).
  • همه‌ی ورودی‌ها را اعتبارسنجی کنید؛ به شناسه یا قیمتی که کلاینت می‌فرستد اعتماد نکنید.
  • Endpointهای عمومی را محدودیت نرخ بزنید و 429 را همراه با Retry-After برگردانید.
  • جزئیات داخلی (خطای SQL، Stack Trace، نسخه‌ی سرور) را افشا نکنید.

مستندسازی

یک سند OpenAPI (Swagger) تولید کنید و مثال‌ها را در آن نگه دارید. مستندات خوب شامل روش احراز هویت، قالب خطا، قوانین صفحه‌بندی و نمونه‌درخواست‌های واقعی است. ابزارها می‌توانند از روی آن کلاینت‌های تایپ‌شده برای TypeScript، C# و اپلیکیشن موبایل بسازند.

جمع‌بندی

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

پرسش‌های متداول

برای به‌روزرسانی PUT یا PATCH؟

وقتی کلاینت کل نمایش منبع را می‌فرستد PUT، و برای به‌روزرسانی بخشی PATCH. بسیاری از APIها فقط به‌روزرسانی بخشی به سبک PATCH را پیاده می‌کنند؛ فقط یکدست باشید.

با وجود GraphQL و gRPC آیا REST هنوز مطرح است؟

بله. REST ساده‌ترین و پرپشتیبانی‌ترین گزینه برای APIهای عمومی و وب است. GraphQL برای نیازهای داده‌ای پیچیده‌ی سمت کلاینت و gRPC برای ارتباط داخلی سرویس‌ها مناسب‌اند.

برای منبعی که کاربر به آن دسترسی ندارد 404 برگردانم یا 403؟

برگرداندن 404 لو نمی‌دهد که منبع وجود دارد. بسیاری از APIها برای منابع خصوصی همین را ترجیح می‌دهند.