แนะนำแนวทางการออกแบบ APIs เพื่อคนในทีม

Published on
Web Development
2020/02/restful-api-guideline
Discord

สวัสดีครับ บทความนี้ผมจะมาพูดเรื่อง API และการออกแบบ API กันนะครับ โดยไม่ได้มีการเขียนโค๊ด แต่จะเป็นพวก Concept เป็นแนวทางการปฎิบัติ ซะมากกว่า มองว่ามันคือ Guideline นะครับ

และเนื่องจาก ในบางครั้ง ผมหรือคนในทีม เวลาออกแบบ API หลายๆครั้ง ก็รีบกัน บางทีก็ไม่ได้มี API ที่ดี หรือไม่ได้ตกลงกันให้ดีก่อน บางครั้งก็ต้องปรับแก้ตามกันไป และเรื่องของ Consistency อันนี้ก็สำคัญมาก ไม่ใช่ว่า หน้านึงใช้อีกแบบ อีกหน้าเรียกอีกแบบ มั่วไปหมด จึงคิดว่า เราควรมี API Guideline กันได้แล้ว เลยเป็นที่มาของบทความนี้ครับ

บทความนี้พูดถึงแค่ Spec นะครับ ไม่ได้พูดถึงว่า ถ้าเราทำ API โค๊ดส่วน Controller จัดยังไง Service ยังไง มี methodName แบบไหน แต่เราพูดในเชิง API Spec ครับ

REST APIs คือ?

REST นั้นย่อมาจาก Representational State Transfer มันคือรูปแบบการทำงานของ Web Service แบบหนึ่ง โดยอาศัย Resource หรือ HTTP Method มาเป็นตัวกำหนด เช่น GET, PUT, POST, DELETE นั่นเอง

แล้วทีนี้ การทำ APIs เนี้ย มันไม่ได้มีข้อกำหนด หรือต้องทำตามเป๊ะๆ หรอก มันเลยมี Guideline ต่างๆออกมา เพื่อเป็นแนวทางปฎิบัติเพื่อให้ในทีมไปในทิศทางเดียวกัน เช่น ของ Microsoft หรือ Google ก็มี

นอกจากนี้ การทำ API Spec จริงๆแล้ว ก็ยังมี OpenAPI หรือการใช้ Swagger เข้ามาช่วยด้วยเช่นกัน

แต่ทั้งนี้ทั้งนั้น การออกแบบ API ก็เป็นเพียงแค่แนวทางในการปฎิบัติ เพื่อให้ทีมเป็นไปในแนวทางเดียวกันเท่านั้นนะครับ ไม่มีผิด ไม่มีถูก แต่ถ้าเราเลือกใช้ Guideline ข้อดีคือมันช่วยให้ทุกคนในทีมปฎิบัติ และเข้าใจร่วมกันครับ

และในบทความนี้ก็เป็นตัวอย่าง APIs ที่ผมอ้างอิงจาก Guideline ต่างๆ และนำมาปรับแต่งตามความเห็นคนในทีมครับ ก็เลยนำมาแชร์ให้เพื่อนๆได้อ่านกัน

รวมถึงถ้าเพื่อนๆอยากดูแนวทางการทำ API ลองดูจากพวกเว็บอื่นๆ ที่เค้ามี API Documentation ครับ รายละเอียดก็จะมีต่างกันบ้าง แต่ส่วนใหญ่แล้วก็จะเป็นมาตรฐานเดียวกันครับ

1. สิ่งที่ควรคำนึงในการทำ API Design

ผมยกหัวข้อนี้ขึ้นมาเป็นอันดับแรก เพราะ ก่อนที่เราจะ Design เราควรจะกำหนด ก่อนว่า API นอกจากจะมีอะไรบ้างแล้ว สิ่งที่เราควรคำนึงถึงคือ มาตรฐานของเรานั่นเอง โดยหัวข้อหลักๆแล้วก็เป็น

  • แน่นอน Content Type ก็ต้องเป็น application/json (แต่จริงๆ Media Type ก็มีชนิดอื่นๆเช่นกัน) RFC6838
  • Datetime / Timestamp ควรกำหนดเลยว่าเราจะใช้แบบไหน Timestamp อาจจะกำหนดเป็น ISO 8601 ไปเลยก็ได้
  • OAuth2 token ส่งไปกับ Header แบบไหน หรือ access_token หรือ token อื่นๆ ใช้ key อะไร?
  • กรณี success รูปแบบที่แสดงจะเป็นแบบไหน?
  • กรณี error จะเป็นรูปแบบไหน ต้องมี status code หรือ developer code ด้วยมั้ย?
  • query string หรือค่า default เช่น page, sort, search, q อะไรพวกนี้
  • Naming Conventions อันนี้ก็สำคัญ จะใช้ camel case, snake_case เป็น key

2. API Versioning

เราจะใช้ version ในการแบ่ง API ของเรา เผื่อกรณี ถ้าหากมีการแก้ไข API ไปแล้ว ทั้งเวอร์ชั่นเก่าและใหม่ต้องใช้งานได้ทั้งคู่ เพราะไม่งั้นมีผลกับ Mobile App กรณี API เปลี่ยนแต่ App ยังเป็นเวอร์ชั่นเก่า API ก็จะพัง ตัวอย่างเช่น

# version 1
GET https://api.devahoy.com/v1/products

# version 2
GET https://api.devahoy.com/v2/products

3. HTTP Methods

เราจะไม่ใช้ Verb ใน Endpoint ของเรา เช่น /getUserDetail , createProduct แบบนี้ไม่เอา

แต่เราจะใช้ HTTP Method แทน และ Endpoint ควรจะเป็น Noun และ Plural เช่น /posts , /users . /posts/10/comments

  • GET สำหรับดึงข้อมูล เช่นหน้าลิสต์รายการ หรือหน้ารายละเอียด
  • POST สำหรับการเพิ่มข้อมูล
  • PUT สำหรับการอัพเดทข้อมูล (แบบทั้ง Object)
  • PATCH สำหรับอัพเดทข้อมูล (บางส่วน)
  • DELETE สำหรับลบข้อมูล

สำหรับ PUT กับ PATCH เราจะใช้ PUT กรณีที่อัพเดทข้อมูลทั้งก้อนและใช้ PATCH กรณีอัพเดทแค่ไม่กี่ค่า เช่น หน้า Setting พวก Toggle ON/OFF เราจะใช้ PATCH ในการอัพเดทข้อมูลแทน

รวมถึงหากข้อมูลมีการ relation กัน เราจะใช้เป็น Nested endpoint แทน จะไม่ใช้ Query String* เช่น ตัวอย่างการเรียกใช้ HTTP Method

หรือจะมองเป็น Collection และ Resource Name ก็ได้

// แสดง comment ทั้งหมดของ Post ที่ #1
GET / v1 / posts / 1 / comments;
// แสดง comment #10 ของ Post ที่​ #2
GET / v1 / posts / 2 / comments / 2;
// สร้าง post ใหม่
POST / v1 / posts;
// เพิ่ม comment ใหม่ ใน Post ที่ #1
POST / v1 / posts / 1 / comments;

หรือหากมีกรณีที่เป็นพวก Action ต่างๆ เช่น Like, Bookmark, Subscribe, Vote, Activate อะไรพวกนี้ เราก็จะใส่ไปใน Endpoint เลย (อาจจะขัดกฎข้อที่เราตั้งไว้ว่า Endpoint จะไม่เป็น Verb ซักนิด) เช่น

// ทำการ Like post ที่ #2 ที่เราชอบ
PUT / v1 / posts / 2 / liked;
// ทำการ Bookmark post เอาไว้อ่าน
PUT / v1 / posts / 2 / bookmark;
// ยกเลิกการ Bookmark post
DELETE / v1 / posts / 2 / bookmark;

4. Parameters

ใช้ Parameters ในการ query แบบมีเงื่อนไข เช่น

GET /products?name=MyProduct

GET /products?sort=-created_at&q=Awesome

ใช้ Plurals เช่น

# ✅ควรใช้แบบนี้
GET /products

# ❌จะไม่ใช้แบบนี้
GET /product

5. HTTP Status code

เมื่อเราใช้ HTTP Methods ในการกำหนด Resource แล้ว เวลาที่เราได้ Response กลับ เราก็ใช้ HTTP Status Code ในการกำหนดเช่นกัน ไม่ใช่ 200 OK หมดทุก request นะครับ (ถ้า GraphQL ก็จะเป็นอีกหนึ่งหัวข้อ API Spec ก็ต่างกันครับ)

กรณี Success เราจะแบ่งแบบนี้

  • 200 OK : เป็นปกติทั่วไปของ request ใช้สำหรับพวก GET หรือ PUT ก็ได้
  • 201 Created : response กลับไปกรณีเราสร้าง data ใหม่ จาก POST request อาจจะส่งแค่ status หรือ data ที่เซฟด้วยก็ได้
  • 204 No Content : ส่วนใหญ่ใช้กรณี DELETE ลบข้อมูล เพื่อบอกว่า success แล้ว และไม่จำเป็นต้องส่งอะไรกลับมา

กรณี Error ละ เราก็แบ่งย่อยได้แบบนี้

  • 400 Bad Request : ปกติผมจะให้อันนี้เป็น general error ครับ พวก request ที่ server ไม่เข้าใจ เช่น JSON ผิด หรือ parameters ไม่ถูกต้องตาม spec
  • 401 Unauthorized : เคสนี้สำหรับ token ผิด หมดอายุ หรือไม่ได้แนบ token มา ทั้งหลาย จะ error 401 กลับไปหมดเลย
  • 403 Forbidden : อันนี้คือผ่าน Authen แต่ว่า user คนนั้นไม่มีสิทธิ์เข้าถึงข้อมูล เช่น user ปกติล็อคอินได้แล้ว แต่ถ้าเค้าไป request หน้าที่มีแต่ admin เท่านั้นที่จะเข้าได้ ก็จะได้ 403 Forbidden
  • 404 Not Found : ใช้กรณีที่ request นั้นไม่มีอยู่ในระบบครับ
  • 429 Too many Request : ใช้กรณีที่เรากำหนด rate limit ไว้ว่า API จะให้เรียกได้ กี่ครั้งต่อนาที ถ้าเกินนั้นก็จะ error

นอกเหนือจาก Error ที่เราคุมได้แล้ว ก็ยังมี Error ที่เป็นส่วนของ Server error ครับ เช่น

  • 500 Internal Server Error : ก็คือ request ถูกต้อง แต่โค๊ดเราพังซักทีนึงแหละ แก้ปัญหาที่เราครับ ไม่ต้องโทษใคร
  • 503 Server Unavailable : ก็ Server เราพังอีกเช่นกัน
  • 502, 504 Bad Gateway Gateway Timeout ส่วนใหญ่จะเป็น error จากพวก Reverse proxy หรือ web server อย่าง NGINX กรณีที่ request ถูก แต่ server เราพัง ตัว NGINX เลยไม่ได้ response กลับไป

6. Pagination

การออกแบบ Pagination ก็ทำได้หลายแบบครับ แบบใช้ query string และ response กลับเป็น total, page ไรพวกนี้ หรือจะใช้ Link Header / Content-Length ครับ หรือ HAL ซึ่งการทำ Pagination อาจจะต้องแยกเป็นอีก Topic เลยว่าเราจะ Design แบบไหน แต่สิ่งสำคัญคือ เราต้องสามารถ query ระบุหน้า จำนวนข้อมูลที่ต้องการแสดงได้ หรืออาจจะใช้ last timestamp เป็นต้น

ตัวอย่างเช่น

GET /products?page=2&limit=50

อาจจะได้ response แบบนี้

{
  "_links": {
    "base": "http://localhost:8080/confluence",
    "context": "",
    "next": "/products?page=3&limit=50",
    "self": "/products?page=2&limit=50"
  },
  "limit": 50,
  "data": [{}]

หรือ

{
  "data" : [{}],
  "pagination":  {
      "previous":  "/products?page=1"
      "next":  "/products?page=3"
  }
}

7. Errors

การ Handle error ก็เป็นส่วนสำคัญเช่นกัน เราจึงจำเป็นต้องออกแบบ API สำหรับกรณี error ต่างๆ นอกเหนือจากการใช้ HTTP Status Code แล้ว อาจจะต้องดูว่า เราอยากให้ User ได้รับข้อมูล error message มากน้อยแค่ไหน เช่น

  • กรณี user ทำการ register เราอยากให้ user รู้เลยว่า แต่ละ field error อะไรบ้าง?
  • หรือ กรณี register error ก็แค่โชว์ common error

ทั้งสองกรณี ก็จะต่างกัน ซึ่งถ้าจะโชว์ แต่ละ field เราก็ต้อง design error ให้มี field name ไปด้วย เช่น

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "invalid query"
}

หรือ

{
  "message": "Invalid",
  "errors": [
    {
      "field": "username",
      "detail": "Username already exist."
    },
    {
      "field": "password",
      "detail": "Password is weak"
    }
  ]
}

ตัวอย่าง Error ของ Facebook

{
  "error": {
    "message": "Syntax error \"Field picture specified more than once. at character 23: id,name,picture,picture",
    "type": "OAuthException",
    "code": 2500,
    "fbtrace_id": "111111"
  }
}

ของ Twitter

{
  "errors": [
    {
      "code": 215,
      "message": "Bad Authentication data."
    }
  ]
}

ก็หวังว่าบทความนี้จะเป็นตัวอย่าง หรือเป็นแนวทางให้ใครหลายๆคน และสำหรับมือใหม่ หรือคนที่กำลังศึกษา Node.js และเจอเรื่อง API ก็จะได้ไม่งงนะครับ ว่าทำไมต้องเป็น /products/:id อะไรพวกนี้ (มีหลายๆคนถามมา) นอกจากนี้ API ในบทความนี้ก็เป็นเพียงแค่ส่วนเล็กๆน้อยๆเท่านั้น หากเพื่อนๆสนใจ สามารถหาอ่าน API เพิ่มเติมได้ ด้านล่างนี้เลย

ขอบคุณครับ

❤️ Happy Coding

Buy Me A Coffee
Authors
Discord