DEV Community

Cover image for การพัฒนา API แบบ Spec-First คืออะไร
Thanawat Wongchai
Thanawat Wongchai

Posted on • Originally published at apidog.com

การพัฒนา API แบบ Spec-First คืออะไร

ข้อผิดพลาดของ API ส่วนใหญ่ไม่ได้เกิดจากการเขียนโค้ดผิด แต่เกิดจากการตกลง contract ไม่ชัดเจน เช่น Frontend รอ userId แต่ Backend ส่ง user_id และเพิ่งเจอปัญหาใน QA การพัฒนา API แบบ Spec-first แก้ปัญหานี้โดยให้ทีมเขียน contract ก่อน แล้วค่อยสร้าง mock, tests, docs และ implementation ตาม contract เดียวกัน

ลองใช้ Apidog วันนี้

ในคู่มือนี้ คุณจะสร้าง 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 เดียวกันได้ ความแตกต่างคือ “ปัญหาจะถูกพบเมื่อไร” และ “ทีมเริ่มงานพร้อมกันได้หรือไม่”

Spec-first vs Code-first lifecycle

ใน 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

จุดที่ควรสังเกต:

  • 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"
Enter fullscreen mode Exit fullscreen mode

จาก spec นี้ ทีม Frontend จะรู้ทันทีว่าเรียกได้แบบนี้:

GET /v1/users?limit=20
Enter fullscreen mode Exit fullscreen mode

และ 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
Enter fullscreen mode Exit fullscreen mode

ตัวอย่าง request:

POST /v1/users
Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode
{
  "email": "alice@example.com",
  "name": "Alice"
}
Enter fullscreen mode Exit fullscreen mode

ตัวอย่าง response ที่ควรได้:

HTTP/1.1 201 Created
Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode
{
  "id": "7f9e8c4a-7b15-4f8e-9a67-1d5f7d7c3f21",
  "email": "alice@example.com",
  "name": "Alice",
  "createdAt": "2026-06-02T10:00:00Z"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

ถึงจุดนี้ 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"
  }
]
Enter fullscreen mode Exit fullscreen mode

ผลลัพธ์คือ 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 ต้องตรงกับ User schema
  • ไม่ส่ง 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
Enter fullscreen mode Exit fullscreen mode

หรือ:

 required:
   - id
-  - email
   - createdAt
Enter fullscreen mode Exit fullscreen mode

ผู้รีวิวจึงเห็น breaking change ได้ก่อน merge และก่อน deploy

ทำได้ใน Apidog

Apidog รองรับ workflow นี้ผ่าน โหมด Spec-First โดยมองไฟล์ OpenAPI เป็นแกนหลักของโปรเจกต์ ไม่ใช่แค่ไฟล์ export หลังจากสร้าง API เสร็จ

Apidog Spec-First Mode

workflow ที่ใช้งานได้จริงคือ:

  1. เขียนหรือวาง OpenAPI spec ลงใน editor
  2. Apidog แยกวิเคราะห์ spec
  3. Mock server ถูกสร้างจาก operation ที่ประกาศไว้
  4. เอกสาร API อัปเดตตาม spec
  5. ทีมเรียกใช้ operation เป็น test case กับ backend จริง
  6. ตรวจว่าการตอบกลับตรงกับ 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:

  1. เก็บ spec ใน Git
  2. รีวิวการเปลี่ยนแปลงผ่าน pull request
  3. สร้าง mock และ docs จาก spec
  4. รัน contract tests ใน CI
  5. ทำให้ build ล้มเหลวเมื่อ response จริงไม่ตรงกับ schema

เมื่อ spec, mock, tests และ docs มาจากไฟล์เดียวกัน โอกาสที่ contract จะคลาดเคลื่อนจาก implementation จะลดลงมาก

สรุป

การพัฒนา API แบบ Spec-first คือการเปลี่ยนลำดับงานเล็กน้อย แต่ลดปัญหา integration ได้มาก: เขียน contract ก่อน ตกลงร่วมกัน แล้วค่อย implement ตามนั้น

ถ้าต้องการทดลอง workflow เต็มรูปแบบ ให้เปิด โหมด Spec-First ใน Apidog แล้วเชื่อมกับ repository ของทีม

Top comments (0)