REST API Design
RESTful API design practices — naming conventions, status codes, versioning, pagination, error handling
What I Can Do
- ออกแบบ RESTful API ที่ consistent, predictable, ใช้งานง่าย
- กำหนด naming conventions, versioning strategy สำหรับทีม
- ออกแบบ error response format ที่ช่วย client debug ได้เร็ว
- Implement pagination, filtering, sorting สำหรับ list endpoints
- เขียน API documentation ที่ developers อ่านแล้วใช้งานได้ทันที
Resource Naming Conventions
ใช้ nouns ไม่ใช่ verbs — resource เป็นพหูพจน์: /users, /orders, /products ไม่ใช่ /getUsers หรือ /createOrder HTTP method บอก action อยู่แล้ว
GET /users— list usersPOST /users— create userGET /users/:id— get specific userPUT /users/:id— replace user (full update)PATCH /users/:id— partial updateDELETE /users/:id— delete user
Nested resources สำหรับ relationships: GET /users/:id/orders — orders ของ user นั้น ไม่ซ้อนเกิน 2 ชั้น — ถ้าลึกกว่านั้นใช้ query parameters แทน
HTTP Methods
- GET — อ่าน data, ต้อง idempotent และ safe (ไม่เปลี่ยน state)
- POST — สร้าง resource ใหม่, ไม่ idempotent (เรียกซ้ำได้ duplicate)
- PUT — replace ทั้ง resource, idempotent (เรียกซ้ำได้ผลเหมือนกัน)
- PATCH — update บาง fields, ส่งเฉพาะ fields ที่ต้องการเปลี่ยน
- DELETE — ลบ resource, idempotent
HTTP Status Codes
ใช้ status codes ให้ตรง semantic — client ตรวจ status code ก่อนอ่าน body
Success (2xx):
200 OK— GET, PUT, PATCH สำเร็จ201 Created— POST สร้าง resource สำเร็จ + return resource ที่สร้าง +Locationheader204 No Content— DELETE สำเร็จ ไม่มี body
Client Errors (4xx):
400 Bad Request— request body/params ไม่ถูก format หรือ validation fail401 Unauthorized— ไม่ได้ login / token หมดอายุ403 Forbidden— login แล้วแต่ไม่มีสิทธิ์404 Not Found— resource ไม่มี409 Conflict— duplicate / state conflict (เช่น email ซ้ำ)422 Unprocessable Entity— format ถูกแต่ business logic ไม่ผ่าน429 Too Many Requests— rate limit exceeded
Server Errors (5xx):
500 Internal Server Error— server error ที่ไม่คาดคิด502 Bad Gateway— upstream service ไม่ตอบ503 Service Unavailable— server overloaded / maintenance
Error Response Format
ใช้ format เดียวกันทุก error — client parse ได้ง่าย ไม่ต้องเดา structure
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "must be a valid email address" },
{ "field": "age", "message": "must be at least 18" }
]
}
}code— machine-readable error code สำหรับ client handle programmaticallymessage— human-readable summarydetails— field-level errors สำหรับ form validation- อย่าส่ง stack trace หรือ internal error ไป client ใน production
Pagination
ใช้ offset-based หรือ cursor-based ขึ้นอยู่กับ use case
Offset-based (เหมาะกับ data ที่ไม่เปลี่ยนบ่อย):
GET /users?page=2&limit=20- Response มี
total,page,limit,total_pages
Cursor-based (เหมาะกับ real-time feeds, data เยอะ):
GET /users?cursor=abc123&limit=20- Response มี
next_cursorสำหรับ request ถัดไป - เร็วกว่า offset เมื่อ data เยอะ เพราะไม่ต้อง COUNT
Filtering & Sorting
Filtering — ใช้ query parameters: GET /orders?status=pending&created_after=2024-01-01
Sorting — ใช้ sort parameter: GET /users?sort=created_at:desc หรือ sort=-created_at (prefix - = descending)
Search — ใช้ q parameter: GET /users?q=pongsakorn สำหรับ full-text search
ไม่ควรรับ raw SQL หรือ complex query language จาก client — กำหนด allowed filters ไว้ใน server
API Versioning
ใช้ URL path versioning: /api/v1/users, /api/v2/users — ชัดเจน, ง่ายต่อ routing, ดูจาก URL รู้เลยว่าใช้ version ไหน
- เพิ่ม version ใหม่เมื่อมี breaking changes เท่านั้น
- Non-breaking changes (เพิ่ม field, เพิ่ม endpoint) ไม่ต้องขึ้น version ใหม่
- Deprecate version เก่าอย่าง graceful — แจ้ง clients ล่วงหน้า, return
Deprecationheader
Authentication & Authorization
- ใช้
Authorization: Bearer <token>header สำหรับ JWT/OAuth - ไม่ส่ง credentials ผ่าน query parameters — จะ log ใน access logs
- แยก authentication (ใครเป็นใคร) กับ authorization (มีสิทธิ์ทำอะไร)
- Return
401เมื่อไม่ authenticate,403เมื่อ authenticate แล้วแต่ไม่มีสิทธิ์
Request & Response Conventions
- ใช้
camelCaseสำหรับ JSON keys:{ "firstName": "Pong", "createdAt": "..." } - Timestamps ใช้ ISO 8601:
"2024-01-15T10:30:00Z" - ใช้ UTC เสมอ — ให้ client แปลง timezone เอง
- Response ของ create/update ควร return resource ที่เปลี่ยนแล้ว — ลด round trips
- ใช้
Content-Type: application/jsonทั้ง request และ response
Rate Limiting
- Return
429 Too Many Requestsเมื่อเกิน limit - ส่ง headers บอก client:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset - Rate limit per user/API key ไม่ใช่ per IP (เพราะ shared IPs)
- กำหนด limit ต่าง tier: free vs paid users
Idempotency
POST ไม่ idempotent — เรียกซ้ำได้ duplicate ใช้ Idempotency-Key header สำหรับ critical operations (payment, transfer)
- Client ส่ง unique key ทุกครั้ง:
Idempotency-Key: uuid-v4 - Server เก็บ key + response ไว้ ถ้าเจอ key ซ้ำ return response เดิม
- สำคัญมากสำหรับ fintech — ป้องกัน double charge
HATEOAS & Resource Links
ใส่ links ใน response เพื่อให้ client navigate ได้โดยไม่ต้อง hardcode URLs — ใช้ในระดับที่เหมาะสม ไม่ต้อง implement ทั้งหมด
{
"id": 1,
"name": "Pongsakorn",
"links": {
"self": "/api/v1/users/1",
"orders": "/api/v1/users/1/orders"
}
}Bulk Operations
เมื่อต้อง create/update/delete หลาย records พร้อมกัน — ไม่ควรให้ client เรียก API ทีละตัว
POST /users/bulk— batch createPATCH /users/bulk— batch update- Return partial success: บอกว่า record ไหนสำเร็จ record ไหน fail พร้อม error details
ปัญหาที่เจอบ่อย & วิธีแก้
ใช้ verbs ใน URL แทน HTTP methods
URL เต็มไปด้วย /getUsers, /createOrder, /deleteUser/:id
สาเหตุ: คิดแบบ RPC แทน REST — เอา action ไปไว้ใน URL แทนที่จะใช้ HTTP method บอก
วิธีแก้:
- ใช้ HTTP method เป็นตัวบอก action:
GET /users,POST /orders,DELETE /users/:id - URL ควรเป็น nouns (resources) เท่านั้น:
/users,/orders,/products - สำหรับ actions ที่ไม่ใช่ CRUD ใช้ sub-resource:
POST /orders/:id/cancelแทน/cancelOrder/:id
Status code ไม่ตรง semantic
Return 200 OK ทุกกรณี แล้วส่ง error ใน body
สาเหตุ: ง่ายกว่าที่จะ return 200 ตลอดแล้วให้ client ตรวจ body — แต่ทำให้ client ต้อง parse body ทุกครั้ง, HTTP caching/proxies ทำงานผิด, monitoring tools จับ error ไม่ได้
วิธีแก้:
- ใช้ status codes ให้ตรง:
201สำหรับ created,404สำหรับ not found,422สำหรับ validation error - กำหนด status code mapping ไว้ในทีม — ทุกคนใช้เหมือนกัน
- Monitoring/alerting ดักจับ 4xx/5xx ได้ตรงจุด
Pagination ไม่มี — return ทุก records
GET /users return users ทั้ง 100,000 records
สาเหตุ: ตอนแรก data น้อยไม่มีปัญหา — พอ data โตก็ช้า, กิน memory ทั้ง server และ client, response timeout
วิธีแก้:
- ใส่ default limit ทุก list endpoint:
limit=20ถ้า client ไม่ส่ง - กำหนด max limit: ไม่เกิน 100 ต่อ request
- Return pagination metadata:
total,page,limit,next_cursor - สำหรับ data เยอะมาก (100K+) ใช้ cursor-based pagination แทน offset
Error response format ไม่ consistent
บาง endpoint return { "error": "..." } บาง endpoint return { "message": "..." } บาง endpoint return string
สาเหตุ: คนละคนเขียนคนละ format, ไม่มี standard, error handling กระจายอยู่ในแต่ละ handler
วิธีแก้:
- กำหนด error response format เดียว ใช้ทั้ง API:
{ "error": { "code": "...", "message": "...", "details": [...] } } - ใช้ centralized error handler middleware — ทุก error ผ่านที่เดียว format เดียว
- สร้าง custom error types ที่มี status code + error code ในตัว
N+1 API calls จาก client
Client เรียก GET /users แล้ว loop เรียก GET /users/:id/orders ทีละ user
สาเหตุ: API ไม่รองรับ include/expand related resources, client ต้องเรียกหลาย endpoints เพื่อประกอบ data
วิธีแก้:
- เพิ่ม query parameter สำหรับ include relations:
GET /users?include=orders - สร้าง composite endpoint สำหรับ use case ที่ใช้บ่อย:
GET /users/with-orders - ใช้ bulk endpoint:
GET /orders?user_ids=1,2,3แทน loop ทีละ user
Breaking changes ไม่ version
เปลี่ยน response format แล้ว client เก่าพัง
สาเหตุ: ไม่มี versioning strategy, แก้ API ตรงๆ โดยไม่คิดว่า client เก่ายังใช้อยู่
วิธีแก้:
- ใช้ URL versioning:
/api/v1/users,/api/v2/users - เพิ่ม fields ใหม่ได้โดยไม่ต้องขึ้น version (additive changes ไม่ breaking)
- เมื่อต้อง breaking change: ขึ้น version ใหม่, แจ้ง deprecation ล่วงหน้า, ให้ v1 ทำงานต่อช่วง transition
- ใส่
Sunsetheader บอก client ว่า version เก่าจะปิดเมื่อไหร่
Related Skills
- Gin — Go web framework สำหรับ implement REST APIs
- Fiber — high-performance Go framework สำหรับ REST APIs
- Node.js — JavaScript runtime สำหรับ backend APIs
- PostgreSQL — database ที่ใช้เก็บ data หลัง API