02. Specs & API Contract First — Vibe Coding Series

Viết API contract trước khi code để frontend và backend build song song — dùng AI tạo OpenAPI spec, mock server, và contract testing.

Sprint đầu tiên của TaskFlow, tôi để frontend và backend chạy song song. Hai tuần sau, khi integrate, tôi mới phát hiện: backend trả về assignee_id (snake_case), frontend expect assigneeId (camelCase). Endpoint tạo task backend dùng POST /tasks, frontend đang gọi POST /api/tasks. Response format cũng khác — backend wrap trong { data: ... }, frontend expect flat object.

Không phải bug. Không phải lỗi logic. Chỉ là hai người (và hai AI agent) đã assume khác nhau về cùng một contract.

Hai ngày fix integration. Hai ngày có thể tránh được hoàn toàn.


Bối cảnh

Ở bài 01, tôi đã đi qua Phase 1: phân tích yêu cầu với AI, tạo user story, và chốt kiến trúc. Bài này bước vào Phase 2: trước khi viết một dòng code nào, chúng ta cần một contract chung — API spec mà cả frontend, backend, và AI agent đều đồng ý.


Tại sao Contract First?

Lý do đơn giản: khi không có contract, mỗi bên tự assume.

Parallel development thực sự song song. Với OpenAPI spec đã được chốt, frontend có thể mock server và build UI trong khi backend implement. Không cần chờ nhau. Không cần “backend xong rồi frontend mới làm được.”

Early validation. Spec là nơi rẻ nhất để phát hiện vấn đề. Thêm một endpoint vào YAML mất 5 phút. Refactor code đã implement mất 5 giờ.

AI có context rõ hơn. Khi bạn feed OpenAPI spec vào AI agent, nó biết chính xác: endpoint nào tồn tại, request/response shape là gì, authentication scheme là gì. Không còn chuyện AI tự bịa endpoint hay assume response format.


Viết spec với AI

Từ requirements → OpenAPI spec

Sau khi có user story từ bài 01, tôi dùng prompt này để tạo spec ban đầu:

Prompt:
"Đây là user stories của tôi: [list]. Hãy tạo OpenAPI 3.0 spec
cho các endpoint cần thiết. Bao gồm: request/response schema,
error codes, authentication scheme (Bearer token), và ví dụ
request/response cho mỗi endpoint."

AI sẽ tạo ra một draft spec. Đây là ví dụ cho endpoint tạo task trong TaskFlow:

openapi: "3.0.3"
info:
  title: TaskFlow API
  version: "1.0.0"

paths:
  /tasks:
    post:
      summary: Create a new task
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title, teamId]
              properties:
                title:
                  type: string
                  minLength: 1
                  maxLength: 255
                description:
                  type: string
                teamId:
                  type: string
                  format: uuid
                assigneeId:
                  type: string
                  format: uuid
                dueDate:
                  type: string
                  format: date-time
      responses:
        "201":
          description: Task created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Task"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

components:
  schemas:
    Task:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        description:
          type: string
        teamId:
          type: string
          format: uuid
        assigneeId:
          type: string
          format: uuid
        createdBy:
          type: string
          format: uuid
        status:
          type: string
          enum: [todo, in_progress, done]
        dueDate:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

AI review spec: tìm lỗ hổng

Sau khi có draft, tôi dùng AI để review chính spec đó:

Prompt:
"Review OpenAPI spec này và tìm:
1. Missing endpoint (có user story nào chưa có endpoint?)
2. Inconsistent naming (camelCase vs snake_case, plural vs singular)
3. Missing error case (scenario nào chưa có error response?)
4. Security issue (endpoint nào cần auth nhưng chưa có?)"

Với TaskFlow, AI phát hiện: tôi có endpoint POST /tasks nhưng thiếu GET /tasks (list tasks của team), và endpoint assign task chưa có — dù user story đã có từ bài 01.

Versioning strategy đơn giản

Cho MVP, tôi dùng URL versioning: /api/v1/tasks. Đơn giản, dễ debug, dễ deprecate sau này. Không cần header versioning hay content negotiation ở giai đoạn này.


Mock server và contract testing

Dùng AI generate mock data

Với spec đã có, tôi yêu cầu AI tạo mock data:

Prompt:
"Dựa trên OpenAPI spec này, tạo mock data cho 5 tasks với
các trạng thái khác nhau (todo, in_progress, done), 3 users,
và 2 teams. Format JSON, realistic data."

Sau đó dùng Prism để spin up mock server từ spec:

npx @stoplight/prism-cli mock openapi.yaml
# Mock server chạy tại http://localhost:4010

Frontend có thể gọi http://localhost:4010/api/v1/tasks ngay lập tức — không cần backend implement xong.

Contract testing với Dredd

Dredd chạy request thực tế vào API và validate response có khớp với spec không:

npx dredd openapi.yaml http://localhost:3000

Khi backend implement xong, chạy Dredd để verify contract. Nếu response shape thay đổi mà spec chưa update — Dredd sẽ fail. Đây là safety net tự động.

Với team nhỏ, Dredd đủ dùng. Nếu cần consumer-driven contract testing (nhiều service, nhiều consumer), Pact là lựa chọn mạnh hơn — nhưng setup phức tạp hơn đáng kể.


AI_CONTEXT.md: file context cho AI agent

Đây là thứ tôi thêm vào workflow sau khi bị “integration hell” lần đầu. AI_CONTEXT.md là file tóm tắt context cho AI agent — đặt ở root project, feed vào mỗi session làm việc.

# TaskFlow — AI Context

## Project Overview
Task management SaaS for indie teams. Multi-tenant, role-based access.
Stack: Next.js 15, Supabase (PostgreSQL + Auth), Resend, Vercel.

## API Contract
- Base URL: /api/v1
- Auth: Bearer token (Supabase JWT)
- Response format: { data: T } for success, { error: string, code: string } for errors
- Naming: camelCase for all JSON fields
- See: /docs/openapi.yaml for full spec

## Data Models (simplified)
- User: { id, email, name, avatarUrl }
- Team: { id, name, ownerId }
- Task: { id, title, description, teamId, assigneeId, createdBy, status, dueDate }
- Status enum: todo | in_progress | done

## Key Constraints
- Users can only access tasks in their teams (enforced by RLS)
- Only task creator or team lead can assign/reassign
- Soft delete for tasks (deletedAt field, not physical delete)
- All timestamps in UTC, ISO 8601 format

## Current Phase
Phase 2 — API spec locked, mock server running.
Backend: implementing endpoints.
Frontend: building UI against mock server.

File này ngắn, cụ thể, và luôn up-to-date. Mỗi khi bắt đầu session mới với AI agent, tôi paste file này vào context. AI không cần đoán — nó biết chính xác project đang ở đâu.


Ví dụ thực chiến: TaskFlow API spec

Ba endpoint chính của TaskFlow MVP:

1. Create task — POST /api/v1/tasks

  • Auth required, user phải là member của team
  • teamId bắt buộc, assigneeId optional
  • Response: task object với id được generate

2. Assign task — PATCH /api/v1/tasks/{taskId}/assign

  • Chỉ task creator hoặc team lead mới được assign
  • Body: { assigneeId: string }
  • Assignee phải là member của cùng team
  • Response: updated task object

3. List tasks — GET /api/v1/tasks?teamId={teamId}

  • Filter theo team (bắt buộc), status (optional), assigneeId (optional)
  • Pagination: ?page=1&limit=20
  • Response: { data: Task[], total: number, page: number }

Sau khi viết spec cho 3 endpoint này, tôi chạy Prism để có mock server. Frontend bắt đầu build task list UI và create task form ngay trong ngày — trong khi backend vẫn đang setup database schema.


Những điều cần tránh

Spec quá chi tiết làm chậm iteration. Ở giai đoạn MVP, đừng spec từng field validation, từng error message. Spec đủ để hai bên đồng ý về shape và contract — chi tiết implementation để code tự document. Over-spec sớm là một dạng premature optimization.

Không update spec khi code thay đổi. Đây là pitfall phổ biến nhất. Backend thêm field mới, đổi tên field, thay đổi response format — nhưng spec vẫn là version cũ. Sau vài tuần, spec và code diverge hoàn toàn. Giải pháp: treat spec như code — mọi thay đổi API phải update spec trước, sau đó mới implement.


Tóm tắt

  • Contract First giúp frontend và backend build song song thực sự — không cần chờ nhau
  • Dùng AI để tạo OpenAPI spec từ user story, sau đó dùng AI review chính spec đó
  • Mock server (Prism) cho phép frontend develop ngay khi backend chưa xong
  • Contract testing (Dredd) là safety net tự động khi backend implement
  • AI_CONTEXT.md là file context ngắn gọn, feed vào AI agent mỗi session — giúp AI không tự assume

Tiếp theo

Phase 2.5 giải quyết vấn đề consistency của UI: khi để AI tự quyết style, mỗi component ra một màu sắc khác nhau. Bài tiếp theo đi vào cách xây design system trước khi code UI — và cách feed design system vào AI context để đảm bảo consistency.

→ Bài 03: Design System & Atomic Components

← Bài 01: Nhận Yêu Cầu & Chốt Kiến Trúc