แนะนำแนวทางการออกแบบ APIs เพื่อคนในทีม
สวัสดีครับ บทความนี้ผมจะมาพูดเรื่อง 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 ไม่ถูกต้องตาม spec401 Unauthorized
: เคสนี้สำหรับ token ผิด หมดอายุ หรือไม่ได้แนบ token มา ทั้งหลาย จะ error 401 กลับไปหมดเลย403 Forbidden
: อันนี้คือผ่าน Authen แต่ว่า user คนนั้นไม่มีสิทธิ์เข้าถึงข้อมูล เช่น user ปกติล็อคอินได้แล้ว แต่ถ้าเค้าไป request หน้าที่มีแต่ admin เท่านั้นที่จะเข้าได้ ก็จะได้ 403 Forbidden404 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
- Authors
- Name
- Chai Phonbopit
- Website
- @Phonbopit