ข้อผิดพลาดของ API ส่วนใหญ่ไม่ได้เกิดจากการเขียนโค้ดผิด แต่เกิดจากการตกลง contract ไม่ชัดเจน เช่น Frontend รอ userId แต่ Backend ส่ง user_id และเพิ่งเจอปัญหาใน QA การพัฒนา API แบบ Spec-first แก้ปัญหานี้โดยให้ทีมเขียน contract ก่อน แล้วค่อยสร้าง mock, tests, docs และ implementation ตาม contract เดียวกัน
ในคู่มือนี้ คุณจะสร้าง OpenAPI spec ขนาดเล็กสำหรับ /users จากนั้นใช้ spec เดียวกันเป็นฐานสำหรับ mock server, contract tests และ API docs ก่อนที่จะมีโค้ดเซิร์ฟเวอร์จริง วิธีนี้มักถูกเรียกว่า spec-driven development, design-first หรือ contract-first ซึ่งทั้งหมดหมายถึงแนวคิดเดียวกัน: ตกลง interface ก่อน แล้วจึง implement ตามนั้น
การพัฒนา API แบบ Spec-first คืออะไร
การพัฒนา API แบบ Spec-first คือการสร้าง contract ที่เครื่องอ่านได้ก่อน implement endpoint โดยทั่วไป contract นี้คือไฟล์ OpenAPI ซึ่งระบุรายละเอียดสำคัญของ API เช่น
- path
- method
- query/path/header parameters
- request body
- response body
- status code
- schema และ validation rule
เมื่อมี spec แล้ว ไฟล์นี้จะกลายเป็นแหล่งความจริงเดียวของทีม:
- Frontend ใช้ mock ที่สร้างจาก spec
- QA เขียน test จาก expected behavior ใน spec
- Backend implement API ให้ตรงกับ spec
- Docs ถูกสร้างจาก spec โดยตรง
ต่างจาก code-first ที่มักเขียน handler ก่อน แล้วค่อยทำเอกสารตามทีหลัง ซึ่งทำให้ docs ล้าสมัยได้ง่าย ใน workflow แบบ spec-first เอกสารไม่ใช่สิ่งที่ตามหลังโค้ด แต่เป็น input ที่ทุกทีมใช้เริ่มงานพร้อมกัน
วงจรชีวิตแบบ Spec-first เทียบกับ Code-first
ทั้งสองแนวทางสามารถสร้าง endpoint เดียวกันได้ ความแตกต่างคือ “ปัญหาจะถูกพบเมื่อไร” และ “ทีมเริ่มงานพร้อมกันได้หรือไม่”
ใน code-first ปัญหามักถูกพบตอน integration หรือ QA เพราะแต่ละทีมตีความ API เองจากโค้ดหรือเอกสารที่อาจไม่อัปเดต แต่ใน spec-first ทีมตกลง contract ตั้งแต่ต้น ทำให้ frontend, backend และ QA ทำงานจาก definition เดียวกัน
ตัวอย่าง OpenAPI ที่ใช้งานได้จริง
เราจะออกแบบ /users endpoint ขนาดเล็ก โดยใช้ OpenAPI 3.0.3 ตัวอย่างนี้มี 2 operation:
-
GET /usersสำหรับดึงรายการผู้ใช้ -
POST /usersสำหรับสร้างผู้ใช้ใหม่
1. สร้าง metadata ของ API
เริ่มจากประกาศเวอร์ชัน OpenAPI, ชื่อ API และ base URL
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
servers:
- url: https://api.example.com/v1
2. กำหนด schema กลางสำหรับ User
เก็บ schema ที่ใช้ซ้ำไว้ใน components/schemas เพื่อให้ทุก endpoint อ้างอิงโครงสร้างเดียวกันด้วย $ref
components:
schemas:
User:
type: object
required:
- id
- email
- createdAt
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
createdAt:
type: string
format: date-time
จุดที่ควรสังเกต:
-
requiredระบุ field ที่ต้องมีใน response -
format: uuid,format: email,format: date-timeช่วยให้ validator และ mock generator สร้างข้อมูลได้ตรงขึ้น - schema กลางช่วยลดปัญหา copy/paste แล้ว field ไม่ตรงกัน
3. เพิ่ม GET /users
GET /users รองรับ query parameter ชื่อ limit และคืนค่าเป็น array ของ User
paths:
/users:
get:
summary: List users
operationId: listUsers
parameters:
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
"200":
description: A list of users
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
จาก spec นี้ ทีม Frontend จะรู้ทันทีว่าเรียกได้แบบนี้:
GET /v1/users?limit=20
และ response ต้องเป็น array ของ object ที่ตรงกับ User schema
4. เพิ่ม POST /users
POST /users ใช้สำหรับสร้างผู้ใช้ใหม่ โดย client ต้องส่ง email และอาจส่ง name ได้
paths:
/users:
post:
summary: Create a user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
properties:
email:
type: string
format: email
name:
type: string
responses:
"201":
description: User created
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"400":
description: Invalid request body
ตัวอย่าง request:
POST /v1/users
Content-Type: application/json
{
"email": "alice@example.com",
"name": "Alice"
}
ตัวอย่าง response ที่ควรได้:
HTTP/1.1 201 Created
Content-Type: application/json
{
"id": "7f9e8c4a-7b15-4f8e-9a67-1d5f7d7c3f21",
"email": "alice@example.com",
"name": "Alice",
"createdAt": "2026-06-02T10:00:00Z"
}
5. รวมเป็นไฟล์ OpenAPI ฉบับสมบูรณ์
เมื่อประกอบทุกส่วนเข้าด้วยกัน จะได้ spec ที่พร้อมใช้กับ mock, tests และ docs
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
servers:
- url: https://api.example.com/v1
paths:
/users:
get:
summary: List users
operationId: listUsers
parameters:
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
"200":
description: A list of users
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
post:
summary: Create a user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
properties:
email:
type: string
format: email
name:
type: string
responses:
"201":
description: User created
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"400":
description: Invalid request body
components:
schemas:
User:
type: object
required:
- id
- email
- createdAt
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
createdAt:
type: string
format: date-time
ถึงจุดนี้ contract ระบุชัดเจนแล้วว่า:
- field ชื่ออะไร
- field ไหนจำเป็น
-
emailต้องมีรูปแบบ email -
limitเป็น integer และไม่เกิน 100 -
POST /usersสำเร็จต้องคืน201 - request ที่ไม่ถูกต้องต้องมี response
400
ยังไม่ต้องมีโค้ดเซิร์ฟเวอร์จริง แต่ทุกทีมเริ่มทำงานจาก contract เดียวกันได้แล้ว
สร้าง mocks, tests และ docs จาก spec
เหตุผลหลักของ spec-first คือการใช้ไฟล์เดียวสร้าง artifact หลายอย่างที่ทีมมักทำแยกกัน
1. Mock server
Mock server อ่าน OpenAPI spec แล้วสร้าง response ตัวอย่างให้ตรงกับ schema
สำหรับ GET /users mock server สามารถคืน response ที่มีโครงสร้างแบบนี้:
[
{
"id": "b4f4e6e4-9bb8-4d34-bc71-5d3f5e8b5ef1",
"email": "user@example.com",
"name": "User Name",
"createdAt": "2026-06-02T10:00:00Z"
}
]
ผลลัพธ์คือ Frontend สามารถเรียก API ได้ทันที แม้ Backend ยังไม่ได้ implement handler จริง เมื่อ spec เปลี่ยน mock ก็เปลี่ยนตาม contract ใหม่
2. Contract tests
เพราะ spec ระบุ required fields, data types และ status codes แล้ว จึงสามารถใช้เป็น test oracle ได้
ตัวอย่าง test case ที่ควรมีสำหรับ POST /users:
- ส่ง
emailถูกต้อง ต้องได้201 - response body ต้องตรงกับ
Userschema - ไม่ส่ง
emailต้องได้400 - field
idต้องเป็นuuid - field
createdAtต้องเป็นdate-time
แนวคิดสำคัญคือ test ไม่ได้เดาจาก implementation แต่ตรวจว่า implementation ทำตาม contract ที่ตกลงไว้หรือไม่
3. API docs
API docs สามารถ render จาก OpenAPI spec เดียวกันได้โดยตรง ทุก endpoint, parameter, request body และ response schema มาจาก YAML ไฟล์เดียว ไม่ต้องดูแลเอกสารอีกชุดแยกจากโค้ด
วิธีนี้เข้ากันได้ดีกับ เวิร์กโฟลว์ API ที่เป็น Git-native เพราะ spec เป็นไฟล์ข้อความธรรมดา การเปลี่ยนแปลง contract จึงเห็นได้เป็น diff ใน pull request เช่น:
- user_id:
+ userId:
type: string
หรือ:
required:
- id
- - email
- createdAt
ผู้รีวิวจึงเห็น breaking change ได้ก่อน merge และก่อน deploy
ทำได้ใน Apidog
Apidog รองรับ workflow นี้ผ่าน โหมด Spec-First โดยมองไฟล์ OpenAPI เป็นแกนหลักของโปรเจกต์ ไม่ใช่แค่ไฟล์ export หลังจากสร้าง API เสร็จ
workflow ที่ใช้งานได้จริงคือ:
- เขียนหรือวาง OpenAPI spec ลงใน editor
- Apidog แยกวิเคราะห์ spec
- Mock server ถูกสร้างจาก operation ที่ประกาศไว้
- เอกสาร API อัปเดตตาม spec
- ทีมเรียกใช้ operation เป็น test case กับ backend จริง
- ตรวจว่าการตอบกลับตรงกับ schema ที่ประกาศไว้
สำหรับตัวอย่าง /users ด้านบน ทีม Frontend สามารถเริ่มเรียก mock ของ GET /users และ POST /users ได้ทันที ขณะที่ Backend implement API จริงตาม contract เดียวกัน
จุดสำคัญอีกอย่างคือการซิงค์ Git แบบสองทาง Spec อยู่ใน repository และการเปลี่ยนแปลงไหลได้ทั้งสองทิศทาง:
- แก้ YAML ใน editor แล้ว push เข้า Git → Apidog รับการเปลี่ยนแปลง
- แก้ใน Apidog → การเปลี่ยนแปลงปรากฏเป็น commit ที่ทีมรีวิวได้
วิธีนี้ช่วยลดปัญหา “มี contract สองชุด” เพราะ spec ยังคงเป็น source of truth เดียว หากต้องการดูรายละเอียดความแตกต่างเชิงแนวคิด อ่านเพิ่มเติมได้ที่ spec-first vs design-first ใน Apidog
รายการตรวจสอบ Spec-first
ก่อนเริ่ม implement ให้ตรวจ spec ด้วย checklist นี้:
- [ ] Spec validate ผ่าน OpenAPI schema โดยไม่มี error
- [ ] ทุก endpoint มี success response
- [ ] ทุก endpoint มี error response อย่างน้อยหนึ่งรายการ
- [ ] Schema ที่ใช้ซ้ำถูกเก็บใน
components/schemas - [ ] ใช้
$refแทนการ copy schema ซ้ำ - [ ] Field ที่จำเป็นถูกกำหนดใน
required - [ ] Field สำคัญมี
formatเช่นuuid,email,date-time - [ ] Request body ระบุ
required: trueเมื่อจำเป็น - [ ] Status code สอดคล้องกับ behavior เช่น
201สำหรับ create - [ ] Spec ถูก commit เข้า version control
- [ ] การเปลี่ยนแปลง spec ถูกรีวิวใน pull request
- [ ] Mock server ทำงานจาก spec
- [ ] Frontend เรียก mock ได้
- [ ] Contract tests ตรวจ response จริงเทียบกับ schema
- [ ] Docs render จาก spec เดียวกัน ไม่มีเอกสารอีกชุดที่ต้อง sync เอง
ถ้าครบทุกข้อ ทีมสามารถเริ่ม build พร้อมกันได้จากข้อตกลงเดียว แทนที่จะให้แต่ละทีมเดา API คนละแบบ
คำถามที่พบบ่อย (FAQ)
การพัฒนา API แบบ Spec-first เหมือนกับ Design-first หรือไม่?
ส่วนใหญ่แล้วใช่ คำว่า design-first, contract-first และ spec-first อธิบายหลักการเดียวกันคือกำหนด interface ก่อน implement
คำว่า spec-first จะเจาะจงกว่า เพราะระบุว่า artifact หลักคือไฟล์ OpenAPI spec ที่ทีมใช้เป็น source of truth
ฉันต้องเขียน YAML ด้วยตนเองหรือไม่?
ไม่จำเป็น คุณสามารถสร้าง spec ผ่าน visual editor แล้วให้เครื่องมือสร้าง YAML ให้ หรือจะเขียน YAML โดยตรงก็ได้ จุดสำคัญไม่ใช่วิธีพิมพ์ spec แต่คือการมี contract ที่ชัดเจนและตกลงกันก่อนเขียนโค้ด
หลายทีมใช้ทั้งสองแบบร่วมกัน เช่น ร่าง endpoint ใน visual editor แล้วปรับรายละเอียดใน YAML ระหว่างรีวิว
จะป้องกันไม่ให้ spec และโค้ดคลาดเคลื่อนจากกันได้อย่างไร?
ให้ spec เป็น source of truth และบังคับใช้ใน workflow:
- เก็บ spec ใน Git
- รีวิวการเปลี่ยนแปลงผ่าน pull request
- สร้าง mock และ docs จาก spec
- รัน contract tests ใน CI
- ทำให้ build ล้มเหลวเมื่อ response จริงไม่ตรงกับ schema
เมื่อ spec, mock, tests และ docs มาจากไฟล์เดียวกัน โอกาสที่ contract จะคลาดเคลื่อนจาก implementation จะลดลงมาก
สรุป
การพัฒนา API แบบ Spec-first คือการเปลี่ยนลำดับงานเล็กน้อย แต่ลดปัญหา integration ได้มาก: เขียน contract ก่อน ตกลงร่วมกัน แล้วค่อย implement ตามนั้น
ถ้าต้องการทดลอง workflow เต็มรูปแบบ ให้เปิด โหมด Spec-First ใน Apidog แล้วเชื่อมกับ repository ของทีม


Top comments (0)