مقالات › معماری نرمافزار
اصول طراحی 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ها برای منابع خصوصی همین را ترجیح میدهند.