Skip to content
← All Skills
🔗

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 users
  • POST /users — create user
  • GET /users/:id — get specific user
  • PUT /users/:id — replace user (full update)
  • PATCH /users/:id — partial update
  • DELETE /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 ที่สร้าง + Location header
  • 204 No Content — DELETE สำเร็จ ไม่มี body

Client Errors (4xx):

  • 400 Bad Request — request body/params ไม่ถูก format หรือ validation fail
  • 401 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

json
{
  "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 programmatically
  • message — human-readable summary
  • details — 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 Deprecation header

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 ทั้งหมด

json
{
  "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 create
  • PATCH /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
  • ใส่ Sunset header บอก 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