article

ScalarDocs ที่ทำให้ API documentation กลายเป็นสิ่งที่น่าอ่าน

15 min read

วันที่เจอ API docs ที่แย่ที่สุด

วันหนึ่งต้องใช้ third-party API ดู documentation แล้วอึ้ง:

GET /api/users/{id}

Description: Get user by ID

Parameters:
- id (required): User ID

Response:
- 200: Success
- 404: Not found

แค่นี้! 😱

คำถามที่มี:

  • id เป็น string หรือ number?
  • Response format เป็นยังไง?
  • Authentication ต้องการไหม?
  • Error format เป็นยังไง?
  • Rate limiting มีไหม?

ลองเรียก API ก็ error 401 แต่ไม่บอกว่าต้องส่ง header อะไร ใช้เวลา 2 ชั่วโมงแก้ปัญหาที่ documentation ควรบอก!

ตอนนั้นแหละที่คิดว่า: “เวลาเราทำ API docs ต้องไม่ให้ใครเจอปัญหาแบบนี้!”

ช่วงมืดของ API Documentation

ในโปรเจคเก่าๆ เราใช้วิธีเขียน docs แบบนี้:

Swagger/OpenAPI แบบเขียนมือ

# swagger.yaml - เขียนมือ 500+ บรรทัด
openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /api/users/{id}:
    get:
      summary: Get user by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            example: 123
      responses:
        '200':
          description: User found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: User not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
    Error:
      type: object  
      properties:
        message:
          type: string

ปัญหาที่เจอ:

  • เขียนนาน แล้วก็ sync กับ code ยาก
  • Developer ไม่ค่อย update docs
  • Example data ไม่จริง
  • UI ดูน่าเบื่อ

README.md แบบ Manual

# API Documentation

## Authentication
Send JWT token in Authorization header: `Bearer <token>`

## Endpoints

### GET /api/users/{id}
Get user by ID

**Parameters:**
- id (integer) - User ID

**Response:**
```json
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com"
}

Errors:

  • 401: Unauthorized
  • 404: User not found

**ปัญหา:**
- ไม่มี interactive testing
- ไม่สามารถลอง API ได้
- ไม่มี validation
- ยาก maintain

## การค้นพบ ScalarDocs

พอได้เห็น ScalarDocs ครั้งแรก รู้สึกว่า "นี่สิคือสิ่งที่หาอยู่!"

**Features ที่ทำให้หลงรัก:**
- **Beautiful UI** - สวยกว่า Swagger UI มาก
- **Interactive** - ลอง API ได้ในหน้า docs
- **Real-time** - sync กับ OpenAPI spec อัตโนมัติ
- **Modern** - responsive, fast, accessible
- **Customizable** - theme และ branding ได้

### Setup แรกกับ Express.js

```javascript
// server.js
const express = require('express');
const swaggerJsdoc = require('swagger-jsdoc');
const { scalar } = require('@scalar/express-api-reference');

const app = express();

// Swagger JSDoc configuration
const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'User API',
      version: '1.0.0',
      description: 'A simple Express User API',
    },
    servers: [
      {
        url: 'http://localhost:3000',
        description: 'Development server',
      },
    ],
  },
  apis: ['./routes/*.js'], // paths to files containing OpenAPI definitions
};

const specs = swaggerJsdoc(options);

// Scalar API Reference
app.use(
  '/docs',
  scalar({
    spec: {
      content: specs,
    },
    theme: 'purple', // หรือ 'blue', 'green', 'orange'
  })
);

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
  console.log('API Docs: http://localhost:3000/docs');
});

JSDoc Comments ใน Route Files

// routes/users.js
const express = require('express');
const router = express.Router();

/**
 * @swagger
 * components:
 *   schemas:
 *     User:
 *       type: object
 *       required:
 *         - name
 *         - email
 *       properties:
 *         id:
 *           type: integer
 *           description: The auto-generated user ID
 *           example: 123
 *         name:
 *           type: string
 *           description: The user's full name
 *           example: "John Doe"
 *         email:
 *           type: string
 *           format: email
 *           description: The user's email address
 *           example: "john@example.com"
 *         createdAt:
 *           type: string
 *           format: date-time
 *           description: Account creation timestamp
 *           example: "2023-01-15T10:30:00Z"
 *     Error:
 *       type: object
 *       properties:
 *         error:
 *           type: string
 *           example: "User not found"
 *         code:
 *           type: string
 *           example: "USER_NOT_FOUND"
 */

/**
 * @swagger
 * /api/users/{id}:
 *   get:
 *     summary: Get user by ID
 *     tags: [Users]
 *     parameters:
 *       - in: path
 *         name: id
 *         schema:
 *           type: integer
 *         required: true
 *         description: Numeric ID of the user to retrieve
 *         example: 123
 *     responses:
 *       200:
 *         description: User found successfully
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/User'
 *             examples:
 *               admin_user:
 *                 summary: Admin user example
 *                 value:
 *                   id: 1
 *                   name: "Admin User"
 *                   email: "admin@example.com"
 *                   role: "admin"
 *                   createdAt: "2023-01-01T00:00:00Z"
 *               regular_user:
 *                 summary: Regular user example  
 *                 value:
 *                   id: 123
 *                   name: "John Doe"
 *                   email: "john@example.com"
 *                   role: "user"
 *                   createdAt: "2023-06-15T14:30:00Z"
 *       401:
 *         description: Authentication required
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 *             example:
 *               error: "Authentication token required"
 *               code: "AUTH_REQUIRED"
 *       404:
 *         description: User not found
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 *             example:
 *               error: "User not found"
 *               code: "USER_NOT_FOUND"
 *     security:
 *       - bearerAuth: []
 */
router.get('/users/:id', async (req, res) => {
  try {
    const user = await getUserById(req.params.id);
    if (!user) {
      return res.status(404).json({
        error: 'User not found',
        code: 'USER_NOT_FOUND'
      });
    }
    res.json(user);
  } catch (error) {
    res.status(500).json({
      error: 'Internal server error',
      code: 'INTERNAL_ERROR'
    });
  }
});

module.exports = router;

Advanced Features ที่เรียนรู้

1. Authentication Examples

// swagger config with security schemes
const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'User API',
      version: '1.0.0',
    },
    components: {
      securitySchemes: {
        bearerAuth: {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT',
          description: 'Enter your JWT token'
        },
        apiKey: {
          type: 'apiKey',
          in: 'header',
          name: 'X-API-Key',
          description: 'Enter your API key'
        }
      }
    },
    security: [
      {
        bearerAuth: []
      }
    ]
  },
  apis: ['./routes/*.js'],
};

/**
 * @swagger
 * /api/auth/login:
 *   post:
 *     summary: User login
 *     tags: [Authentication]
 *     security: [] # No auth required for login
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - email
 *               - password
 *             properties:
 *               email:
 *                 type: string
 *                 format: email
 *                 example: "user@example.com"
 *               password:
 *                 type: string
 *                 format: password
 *                 example: "securePassword123"
 *           examples:
 *             valid_credentials:
 *               summary: Valid login credentials
 *               value:
 *                 email: "john@example.com"
 *                 password: "mypassword123"
 *     responses:
 *       200:
 *         description: Login successful
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 token:
 *                   type: string
 *                   example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
 *                 user:
 *                   $ref: '#/components/schemas/User'
 *       401:
 *         description: Invalid credentials
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 */

2. Complex Request/Response Examples

/**
 * @swagger
 * /api/users:
 *   post:
 *     summary: Create a new user
 *     tags: [Users]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - name
 *               - email
 *               - password
 *             properties:
 *               name:
 *                 type: string
 *                 minLength: 2
 *                 maxLength: 50
 *                 example: "John Doe"
 *               email:
 *                 type: string
 *                 format: email
 *                 example: "john@example.com"
 *               password:
 *                 type: string
 *                 minLength: 8
 *                 format: password
 *                 description: "Password must be at least 8 characters long"
 *                 example: "securePassword123"
 *               profile:
 *                 type: object
 *                 properties:
 *                   bio:
 *                     type: string
 *                     maxLength: 500
 *                     example: "Software developer passionate about API design"
 *                   website:
 *                     type: string
 *                     format: uri
 *                     example: "https://johndoe.dev"
 *                   social:
 *                     type: object
 *                     properties:
 *                       twitter:
 *                         type: string
 *                         example: "@johndoe"
 *                       github:
 *                         type: string
 *                         example: "johndoe"
 *           examples:
 *             minimal_user:
 *               summary: Minimal user creation
 *               value:
 *                 name: "Jane Smith"
 *                 email: "jane@example.com"
 *                 password: "password123"
 *             full_profile:
 *               summary: User with complete profile
 *               value:
 *                 name: "John Developer"
 *                 email: "john@example.com"
 *                 password: "securePass456"
 *                 profile:
 *                   bio: "Full-stack developer with 5 years experience"
 *                   website: "https://johndeveloper.com"
 *                   social:
 *                     twitter: "@johndev"
 *                     github: "johndev"
 *     responses:
 *       201:
 *         description: User created successfully
 *         content:
 *           application/json:
 *             schema:
 *               allOf:
 *                 - $ref: '#/components/schemas/User'
 *                 - type: object
 *                   properties:
 *                     profile:
 *                       type: object
 *                       properties:
 *                         bio:
 *                           type: string
 *                         website:
 *                           type: string
 *                         social:
 *                           type: object
 *       400:
 *         description: Validation error
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 error:
 *                   type: string
 *                   example: "Validation failed"
 *                 details:
 *                   type: array
 *                   items:
 *                     type: object
 *                     properties:
 *                       field:
 *                         type: string
 *                       message:
 *                         type: string
 *             example:
 *               error: "Validation failed"
 *               details:
 *                 - field: "email"
 *                   message: "Email is already in use"
 *                 - field: "password"
 *                   message: "Password must be at least 8 characters"
 */

3. File Upload Documentation

/**
 * @swagger
 * /api/users/{id}/avatar:
 *   post:
 *     summary: Upload user avatar
 *     tags: [Users]
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: integer
 *         example: 123
 *     requestBody:
 *       required: true
 *       content:
 *         multipart/form-data:
 *           schema:
 *             type: object
 *             properties:
 *               avatar:
 *                 type: string
 *                 format: binary
 *                 description: "Avatar image file (JPG, PNG, max 5MB)"
 *               crop:
 *                 type: object
 *                 properties:
 *                   x:
 *                     type: integer
 *                     example: 0
 *                   y:
 *                     type: integer
 *                     example: 0
 *                   width:
 *                     type: integer
 *                     example: 200
 *                   height:
 *                     type: integer
 *                     example: 200
 *           encoding:
 *             avatar:
 *               contentType: image/png, image/jpeg
 *     responses:
 *       200:
 *         description: Avatar uploaded successfully
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 avatarUrl:
 *                   type: string
 *                   format: uri
 *                   example: "https://example.com/avatars/123.jpg"
 *                 thumbnails:
 *                   type: object
 *                   properties:
 *                     small:
 *                       type: string
 *                       example: "https://example.com/avatars/123_small.jpg"
 *                     medium:
 *                       type: string
 *                       example: "https://example.com/avatars/123_medium.jpg"
 */

Customization และ Branding

1. Custom Theme

// Custom Scalar configuration
app.use(
  '/docs',
  scalar({
    spec: {
      content: specs,
    },
    configuration: {
      theme: 'purple',
      layout: 'modern', // หรือ 'classic'
      customCss: `
        .scalar-app {
          --scalar-color-1: #1a1a2e;
          --scalar-color-2: #16213e;
          --scalar-color-3: #0f3460;
          --scalar-color-accent: #e94560;
          --scalar-font-size: 14px;
        }
        
        .scalar-app .logo {
          content: url('https://yoursite.com/logo.svg');
        }
        
        .scalar-header {
          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        }
      `,
      metaData: {
        title: 'My Awesome API Documentation',
        description: 'The best API documentation you\'ve ever seen',
        ogDescription: 'Check out our amazing API',
      },
      searchHotKey: 'k',
      hideDownloadButton: false,
      hideTestRequestButton: false,
    },
  })
);

2. Advanced Layout Options

// Multiple documentation versions
app.use('/docs/v1', scalar({
  spec: { content: specsV1 },
  configuration: {
    theme: 'blue',
    metaData: { title: 'API v1.0 Documentation' }
  }
}));

app.use('/docs/v2', scalar({
  spec: { content: specsV2 },
  configuration: {
    theme: 'purple',
    metaData: { title: 'API v2.0 Documentation' }
  }
}));

// Internal vs External docs
app.use('/docs/internal', scalar({
  spec: { content: internalSpecs },
  configuration: {
    authentication: {
      preferredSecurityScheme: 'bearerAuth',
      apiKey: {
        token: process.env.INTERNAL_API_KEY
      }
    }
  }
}));

Integration กับ CI/CD

1. Automated Docs Generation

# .github/workflows/docs.yml
name: Generate API Documentation

on:
  push:
    branches: [main]
    paths: ['routes/**', 'swagger.yaml']

jobs:
  generate-docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          
      - name: Install dependencies
        run: npm install
        
      - name: Generate OpenAPI spec
        run: npm run generate-swagger
        
      - name: Validate OpenAPI spec
        run: npx swagger-codegen-cli validate -i ./swagger.json
        
      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./docs

2. API Diff Detection

// scripts/check-api-changes.js
const swaggerDiff = require('swagger-diff');
const fs = require('fs');

const oldSpec = JSON.parse(fs.readFileSync('./swagger-old.json'));
const newSpec = JSON.parse(fs.readFileSync('./swagger-new.json'));

swaggerDiff(oldSpec, newSpec, {
  check: true,
  markdown: true
}).then(diff => {
  if (diff.breaks > 0) {
    console.error('🚨 Breaking changes detected!');
    console.log(diff.markdown);
    process.exit(1);
  } else if (diff.changes > 0) {
    console.log('✅ Non-breaking changes detected');
    console.log(diff.markdown);
  } else {
    console.log('✅ No API changes');
  }
}).catch(err => {
  console.error('Error comparing API specs:', err);
  process.exit(1);
});

Testing Documentation

1. Documentation Tests

// tests/docs.test.js
const request = require('supertest');
const app = require('../server');
const swaggerSpec = require('../swagger.json');

describe('API Documentation', () => {
  test('should serve documentation at /docs', async () => {
    const response = await request(app)
      .get('/docs')
      .expect(200);
      
    expect(response.text).toContain('Scalar API Reference');
  });
  
  test('should have valid OpenAPI spec', () => {
    expect(swaggerSpec.openapi).toBe('3.0.0');
    expect(swaggerSpec.info).toBeDefined();
    expect(swaggerSpec.paths).toBeDefined();
  });
  
  test('all endpoints should be documented', async () => {
    const routes = getExpressRoutes(app);
    const documentedPaths = Object.keys(swaggerSpec.paths);
    
    routes.forEach(route => {
      const pathExists = documentedPaths.some(path => 
        path.replace(/{([^}]+)}/g, ':$1') === route.path
      );
      expect(pathExists).toBe(true);
    });
  });
});

// Contract testing
describe('API Contract Tests', () => {
  test('GET /api/users/{id} should match documentation', async () => {
    const response = await request(app)
      .get('/api/users/1')
      .set('Authorization', 'Bearer test-token')
      .expect(200);
      
    // Validate response against schema
    const userSchema = swaggerSpec.components.schemas.User;
    expect(response.body).toMatchSchema(userSchema);
  });
});

2. Example Validation

// scripts/validate-examples.js
const Ajv = require('ajv');
const addFormats = require('ajv-formats');

const ajv = new Ajv();
addFormats(ajv);

const validateExamples = (spec) => {
  const errors = [];
  
  Object.entries(spec.paths).forEach(([path, methods]) => {
    Object.entries(methods).forEach(([method, operation]) => {
      // Validate request examples
      if (operation.requestBody) {
        const content = operation.requestBody.content;
        Object.entries(content).forEach(([mediaType, mediaTypeObj]) => {
          if (mediaTypeObj.examples) {
            Object.entries(mediaTypeObj.examples).forEach(([exampleName, example]) => {
              const validate = ajv.compile(mediaTypeObj.schema);
              if (!validate(example.value)) {
                errors.push({
                  path,
                  method,
                  type: 'request',
                  example: exampleName,
                  errors: validate.errors
                });
              }
            });
          }
        });
      }
      
      // Validate response examples
      if (operation.responses) {
        Object.entries(operation.responses).forEach(([status, response]) => {
          if (response.content) {
            Object.entries(response.content).forEach(([mediaType, mediaTypeObj]) => {
              if (mediaTypeObj.example && mediaTypeObj.schema) {
                const validate = ajv.compile(mediaTypeObj.schema);
                if (!validate(mediaTypeObj.example)) {
                  errors.push({
                    path,
                    method,
                    type: 'response',
                    status,
                    errors: validate.errors
                  });
                }
              }
            });
          }
        });
      }
    });
  });
  
  return errors;
};

Real-World Tips ที่เรียนรู้

1. Documentation as Code

// utils/doc-helpers.js - Helper functions สำหรับ documentation
const createApiResponse = (description, schema, examples = {}) => ({
  description,
  content: {
    'application/json': {
      schema,
      examples: Object.keys(examples).length > 0 ? examples : undefined
    }
  }
});

const createErrorResponse = (status, message, code) => ({
  [status]: createApiResponse(
    message,
    { $ref: '#/components/schemas/Error' },
    {
      example: {
        summary: `${status} error example`,
        value: { error: message, code }
      }
    }
  )
});

// Usage in route files
/**
 * @swagger
 * /api/users/{id}:
 *   get:
 *     summary: Get user by ID
 *     responses:
 *       200:
 *         <<: *userSuccessResponse
 *       401:
 *         <<: *authErrorResponse  
 *       404:
 *         <<: *notFoundResponse
 */

2. Versioning Strategy

// Version-specific documentation
const generateVersionedSpec = (version) => {
  const baseSpec = {
    openapi: '3.0.0',
    info: {
      title: 'My API',
      version: version,
      description: `API Documentation for version ${version}`
    }
  };
  
  // Version-specific changes
  switch (version) {
    case 'v1':
      return {
        ...baseSpec,
        paths: v1Paths,
        components: v1Components
      };
    case 'v2':
      return {
        ...baseSpec,
        paths: { ...v1Paths, ...v2Paths },
        components: { ...v1Components, ...v2Components }
      };
  }
};

// Route setup
app.use('/docs/v1', scalar({ 
  spec: { content: generateVersionedSpec('v1') } 
}));
app.use('/docs/v2', scalar({ 
  spec: { content: generateVersionedSpec('v2') } 
}));

3. Interactive Examples

/**
 * @swagger
 * /api/search:
 *   get:
 *     summary: Search users
 *     parameters:
 *       - name: q
 *         in: query
 *         required: true
 *         schema:
 *           type: string
 *           minLength: 2
 *         example: "john"
 *         description: Search query (minimum 2 characters)
 *       - name: limit
 *         in: query
 *         schema:
 *           type: integer
 *           minimum: 1
 *           maximum: 100
 *           default: 10
 *         example: 20
 *       - name: offset
 *         in: query
 *         schema:
 *           type: integer
 *           minimum: 0
 *           default: 0
 *         example: 0
 *       - name: sort
 *         in: query
 *         schema:
 *           type: string
 *           enum: [name, email, created_at]
 *           default: name
 *         example: "name"
 *     responses:
 *       200:
 *         description: Search results
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 users:
 *                   type: array
 *                   items:
 *                     $ref: '#/components/schemas/User'
 *                 pagination:
 *                   type: object
 *                   properties:
 *                     total:
 *                       type: integer
 *                       example: 150
 *                     limit:
 *                       type: integer  
 *                       example: 20
 *                     offset:
 *                       type: integer
 *                       example: 0
 *                     hasMore:
 *                       type: boolean
 *                       example: true
 *             examples:
 *               search_results:
 *                 summary: Typical search results
 *                 value:
 *                   users:
 *                     - id: 1
 *                       name: "John Doe"
 *                       email: "john@example.com"
 *                     - id: 25
 *                       name: "John Smith" 
 *                       email: "johnsmith@example.com"
 *                   pagination:
 *                     total: 2
 *                     limit: 20
 *                     offset: 0
 *                     hasMore: false
 *               empty_results:
 *                 summary: No results found
 *                 value:
 *                   users: []
 *                   pagination:
 *                     total: 0
 *                     limit: 20
 *                     offset: 0
 *                     hasMore: false
 */

Performance และ SEO

1. Static Site Generation

// scripts/generate-static-docs.js
const fs = require('fs');
const path = require('path');
const { renderToString } = require('@scalar/api-reference');

const generateStaticDocs = async () => {
  const spec = require('../swagger.json');
  
  const html = renderToString({
    spec: { content: spec },
    configuration: {
      theme: 'purple',
      layout: 'modern',
      metaData: {
        title: 'My API Documentation',
        description: 'Complete API documentation with examples'
      }
    }
  });
  
  const fullHTML = `
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>API Documentation</title>
  <meta name="description" content="Complete API documentation with examples">
  <meta property="og:title" content="My API Documentation">
  <meta property="og:description" content="Interactive API documentation">
  <meta property="og:type" content="website">
  <link rel="canonical" href="https://api.mysite.com/docs">
</head>
<body>
  ${html}
</body>
</html>`;
  
  fs.writeFileSync('./dist/docs.html', fullHTML);
};

2. CDN และ Caching

// Production optimizations
app.use('/docs', 
  // Cache static assets
  express.static('public', {
    maxAge: '1y',
    etag: false
  }),
  
  scalar({
    spec: { content: specs },
    configuration: {
      // CDN สำหรับ assets
      cdn: 'https://cdn.jsdelivr.net/npm/@scalar/api-reference@latest',
      
      // Preload critical resources
      preloadFonts: true,
      
      // Optimize bundle
      minify: process.env.NODE_ENV === 'production'
    }
  })
);

สรุป: Documentation ที่ทำให้คนอยากใช้ API

ก่อนใช้ ScalarDocs:

  • Documentation น่าเบื่อ และใช้ยาก
  • Developer บ่น API ไม่มี docs
  • Support tickets เยอะเรื่อง API usage
  • Integration ช้าเพราะไม่เข้าใจ API

หลังใช้ ScalarDocs:

  • Documentation สวยและใช้งานง่าย
  • Developer สามารถเริ่มใช้ API ได้เลย
  • Support tickets ลดลง 60%
  • Integration เร็วขึ้นมาก

ข้อดีที่ได้จริง:

  • Beautiful UI ที่ทำให้อยากอ่าน docs
  • Interactive testing ลอง API ได้ในหน้า docs
  • Real-time sync ระหว่าง code และ docs
  • Better DX developer experience ดีขึ้นเยอะ
  • SEO friendly มี meta tags และ structured data

Best Practices ที่เรียนรู้:

  1. Write docs first - design API ผ่าน documentation
  2. Use examples extensively - real data ดีกว่า placeholder
  3. Test your docs - validate examples และ schemas
  4. Keep it updated - automate ให้มาก
  5. Think like users - เขียน docs ให้คนใช้เข้าใจง่าย

ScalarDocs มันเหมือน iPhone ของ API documentation

สวย ใช้งานง่าย และทำให้คนอยากใช้ 📱✨

ตอนนี้ API docs ไม่ใช่สิ่งที่น่าเบื่ออีกต่อไป แต่กลายเป็น selling point ของ API เลย! 🎯