# ตัวอย่าง: โปรเจกต์ E-Commerce API - มาตรฐานการเขียนโค้ด

**เวอร์ชัน:** 1.0  
**อัปเดตครั้งล่าสุด:** มกราคม 2026  
**เจ้าของ:** ทีมพัฒนา Node.js  
**อนุมัติโดย:** Tech Lead และ Scrum Master

---

## 📋 สารบัญ

1. [ภาพรวมโปรเจกต์](#ภาพรวมโปรเจกต์)
2. [ข้อตกลงการตั้งชื่อ](#ข้อตกลงการตั้งชื่อ)
3. [โครงสร้างโค้ด](#โครงสร้างโค้ด)
4. [ความเห็นและเอกสาร](#ความเห็นและเอกสาร)
5. [การจัดการข้อผิดพลาด](#การจัดการข้อผิดพลาด)
6. [มาตรฐานการทดสอบ](#มาตรฐานการทดสอบ)
7. [ความปลอดภัย](#ความปลอดภัย)
8. [Git และ PR](#git-และ-pr)
9. [พบข้อผิดพลาดทั่วไป](#พบข้อผิดพลาดทั่วไป)
10. [แหล่งอ้างอิง](#แหล่งอ้างอิง)

---

## ภาพรวมโปรเจกต์

**ชื่อ:** E-Commerce API  
**ภาษา:** JavaScript (Node.js 18+)  
**Framework:** Express.js  
**ฐานข้อมูล:** PostgreSQL  
**สภาพแวดล้อม:** Dev, Staging, Production

**Tech Stack:**

- **Runtime:** Node.js 18+
- **Framework:** Express.js 4.x
- **ORM:** Sequelize
- **Database:** PostgreSQL 12+
- **Testing:** Jest 29+, Supertest
- **Linting:** ESLint
- **Formatting:** Prettier
- **API:** REST
- **Authentication:** JWT

---

## ข้อตกลงการตั้งชื่อ

### 1. ชื่อคลาส (Class Names)

**ข้อกำหนด:** PascalCase, ใช้คำนาม

✅ **ตัวอย่างที่ดี:**

```javascript
// Service Classes
class UserService {}
class ProductService {}
class OrderService {}

// Repository Classes
class UserRepository {}
class ProductRepository {}

// Controller Classes
class UserController {}
class ProductController {}

// Model Classes
class User {}
class Product {}
class Order {}

// Custom Error Classes
class UserNotFoundError {}
class InvalidEmailError {}
class InsufficientInventoryError {}
```

❌ **ตัวอย่างที่ไม่ดี:**

```javascript
class user {} // lowercase
class USER_SERVICE {} // ไม่ใช่ PascalCase
class getUser {} // ใช้คำกริยา
class us {} // ชื่อสั้นเกินไป
```

### 2. ชื่อเมธอด (Method Names)

**ข้อกำหนด:** camelCase, ใช้คำกริยา

✅ **ตัวอย่างที่ดี:**

```javascript
class UserService {
  // Getters
  getUserById(userId) {}
  findActiveUsers() {}
  getUsersByRole(role) {}

  // Creators
  createUser(userData) {}
  registerNewUser(email, password) {}

  // Updaters
  updateUserProfile(userId, updates) {}
  changeUserPassword(userId, newPassword) {}

  // Deleters
  deleteUser(userId) {}
  deactivateUser(userId) {}

  // Checkers
  isEmailAvailable(email) {}
  hasUserPermission(userId, permission) {}
  canDeleteOrder(userId, orderId) {}

  // Actions
  sendVerificationEmail(userId) {}
  processPayment(orderId, amount) {}
}
```

❌ **ตัวอย่างที่ไม่ดี:**

```javascript
class UserService {
  User() {} // ใช้คำนาม
  GET_USER() {} // ไม่ใช่ camelCase
  user_by_id() {} // snake_case
  u() {} // ชื่อสั้นเกินไป
  getX() {} // ชื่อไม่ชัดเจน
}
```

### 3. ชื่อตัวแปร (Variable Names)

**ข้อกำหนด:** camelCase, ชื่ออธิบายได้

✅ **ตัวอย่างที่ดี:**

```javascript
// Good: ชื่อชัดเจน
const totalPrice = productPrice + tax + shipping;
const isUserActive = user.status === "active";
const maxRetryCount = 3;
const userEmailVerified = true;
const createdAt = new Date();

// Good: ชื่อสำหรับลูป
for (let i = 0; i < items.length; i++) {}
items.forEach((item, index) => {});
```

❌ **ตัวอย่างที่ไม่ดี:**

```javascript
const tp = productPrice + tax + shipping; // สั้นเกินไป
const x = y + z; // ไม่ชัดเจน
const data = getUserInfo(); // "data" เป็นคำทั่วไปเกินไป
const temp = calculateTotal(); // "temp" ไม่ใช่ชื่อถาวร
const UserActive = true; // PascalCase สำหรับตัวแปร
```

### 4. ชื่อค่าคงที่ (Constants)

**ข้อกำหนด:** UPPER_SNAKE_CASE

✅ **ตัวอย่างที่ดี:**

```javascript
// Global Constants
const MAX_PASSWORD_LENGTH = 100;
const MIN_PASSWORD_LENGTH = 8;
const DEFAULT_PAGE_SIZE = 20;
const MAX_LOGIN_ATTEMPTS = 5;
const LOGIN_TIMEOUT_MINUTES = 30;

// Status Constants
const USER_STATUS = {
  ACTIVE: "active",
  INACTIVE: "inactive",
  PENDING: "pending",
  SUSPENDED: "suspended",
};

const ORDER_STATUS = {
  PENDING: "pending",
  PROCESSING: "processing",
  SHIPPED: "shipped",
  DELIVERED: "delivered",
  CANCELLED: "cancelled",
};
```

### 5. ชื่อไฟล์และโฟลเดอร์ (Files & Folders)

**ข้อกำหนด:** camelCase สำหรับไฟล์ JavaScript

✅ **ตัวอย่างที่ดี:**

```
src/
├── controllers/
│   ├── userController.js
│   ├── productController.js
│   └── orderController.js
├── services/
│   ├── userService.js
│   ├── productService.js
│   └── orderService.js
├── repositories/
│   ├── userRepository.js
│   └── productRepository.js
├── models/
│   ├── user.js
│   ├── product.js
│   └── order.js
├── middleware/
│   ├── authMiddleware.js
│   └── errorHandler.js
├── utils/
│   ├── dateUtils.js
│   ├── validationUtils.js
│   └── emailUtils.js
├── tests/
│   ├── unit/
│   │   └── userService.test.js
│   └── integration/
│       └── userAPI.test.js
└── config/
    └── database.js
```

### 6. ชื่อฐานข้อมูล (Database Names)

**ข้อกำหนด:** snake_case สำหรับตารางและคอลัมน์

✅ **ตัวอย่างที่ดี:**

```sql
-- Tables (singular)
CREATE TABLE user (
  id SERIAL PRIMARY KEY,
  user_email VARCHAR(255) UNIQUE NOT NULL,
  first_name VARCHAR(100),
  last_name VARCHAR(100),
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE product (
  id SERIAL PRIMARY KEY,
  product_name VARCHAR(255) NOT NULL,
  product_price DECIMAL(10, 2),
  inventory_count INT,
  is_active BOOLEAN DEFAULT TRUE
);

CREATE TABLE order (
  id SERIAL PRIMARY KEY,
  user_id INT REFERENCES user(id),
  total_amount DECIMAL(10, 2),
  order_status VARCHAR(50),
  created_at TIMESTAMP DEFAULT NOW()
);
```

---

## โครงสร้างโค้ด

### โครงสร้างไดเรกทอรี

```
e-commerce-api/
├── src/
│   ├── config/
│   │   ├── database.js           (Database configuration)
│   │   ├── environment.js        (Environment variables)
│   │   └── constants.js          (Application constants)
│   │
│   ├── controllers/
│   │   ├── userController.js
│   │   ├── productController.js
│   │   └── orderController.js
│   │
│   ├── services/
│   │   ├── userService.js
│   │   ├── productService.js
│   │   ├── orderService.js
│   │   └── emailService.js
│   │
│   ├── repositories/
│   │   ├── userRepository.js
│   │   ├── productRepository.js
│   │   └── orderRepository.js
│   │
│   ├── models/
│   │   ├── user.js
│   │   ├── product.js
│   │   └── order.js
│   │
│   ├── middleware/
│   │   ├── authMiddleware.js
│   │   ├── validationMiddleware.js
│   │   ├── errorHandler.js
│   │   └── requestLogger.js
│   │
│   ├── routes/
│   │   ├── userRoutes.js
│   │   ├── productRoutes.js
│   │   └── orderRoutes.js
│   │
│   ├── validators/
│   │   ├── userValidator.js
│   │   ├── productValidator.js
│   │   └── orderValidator.js
│   │
│   ├── utils/
│   │   ├── dateUtils.js
│   │   ├── emailUtils.js
│   │   └── passwordUtils.js
│   │
│   ├── errors/
│   │   ├── ApplicationError.js
│   │   ├── ValidationError.js
│   │   └── NotFoundError.js
│   │
│   ├── app.js               (Express app setup)
│   └── server.js            (Server startup)
│
├── tests/
│   ├── unit/
│   │   ├── services/
│   │   │   └── userService.test.js
│   │   └── utils/
│   │       └── emailUtils.test.js
│   │
│   ├── integration/
│   │   ├── user.test.js
│   │   ├── product.test.js
│   │   └── order.test.js
│   │
│   └── fixtures/
│       ├── testUsers.json
│       └── testProducts.json
│
├── migrations/
│   ├── 001_create_user_table.sql
│   └── 002_create_product_table.sql
│
├── .env.example
├── .eslintrc.json
├── .prettierrc.json
├── jest.config.js
├── package.json
└── README.md
```

### การจัดองค์กรภายในคลาส

```javascript
class UserService {
  // 1. Static Constants
  static MIN_PASSWORD_LENGTH = 8;
  static MAX_PASSWORD_LENGTH = 100;
  static EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

  // 2. Constructor
  constructor(userRepository, emailService) {
    this.userRepository = userRepository;
    this.emailService = emailService;
  }

  // 3. Public Methods - Main Operations
  async registerUser(email, password, firstName, lastName) {
    this._validateEmail(email);
    this._validatePassword(password);

    const existingUser = await this.userRepository.findByEmail(email);
    if (existingUser) {
      throw new Error("Email already registered");
    }

    const hashedPassword = await this._hashPassword(password);
    const user = await this.userRepository.create({
      email,
      password: hashedPassword,
      firstName,
      lastName,
    });

    await this.emailService.sendVerificationEmail(user.id, email);
    return { id: user.id, email: user.email };
  }

  async getUserById(userId) {
    const user = await this.userRepository.findById(userId);
    if (!user) {
      throw new NotFoundError("User not found");
    }
    return user;
  }

  async updateUserProfile(userId, updates) {
    const user = await this.getUserById(userId);
    const updatedUser = await this.userRepository.update(userId, updates);
    return updatedUser;
  }

  // 4. Private Methods - Helper Functions
  _validateEmail(email) {
    if (!this.constructor.EMAIL_REGEX.test(email)) {
      throw new ValidationError("Invalid email format");
    }
  }

  _validatePassword(password) {
    if (password.length < this.constructor.MIN_PASSWORD_LENGTH) {
      throw new ValidationError("Password too short");
    }
  }

  async _hashPassword(password) {
    // Implementation
  }
}
```

---

## ความเห็นและเอกสาร

### ความเห็นในโค้ด

**ข้อกำหนด:** ใช้ความเห็น **WHY** ไม่ใช่ **WHAT**

✅ **ตัวอย่างที่ดี:**

```javascript
// ทำไม: อธิบายเหตุผล
async validateUserEmail(email) {
  // ตรวจสอบแบบสั้นเพื่อป้องกันการทำให้ไม่ระบุตัวตน (enumeration attack)
  const exists = await User.findOne({ where: { email } });
  return exists !== null;
}

// ทำไม: อธิบายการตัดสินใจ
if (user.loginAttempts >= 5) {
  // ล็อก user หลังจาก 5 ครั้ง เพื่อป้องกัน brute force
  user.isLocked = true;
}

// ทำไม: เน่ืองจากประเด็น
const MAX_RETRY = 3;
// ลองใหม่ 3 ครั้ง เพื่อจัดการ transient errors จากเซิร์ฟเวอร์ payment gateway
```

❌ **ตัวอย่างที่ไม่ดี:**

```javascript
// WHAT: ชัดเจนแล้วจากโค้ด
user.lastLogin = new Date(); // ตั้งค่า lastLogin

// WHAT: ความเห็นที่ล้าสมัย
const status = "active"; // TODO: จะเปลี่ยนเป็น enum

// WHAT: ความเห็นที่ชัดเจนแล้ว
x++; // เพิ่ม x
```

### JSDoc / เอกสารฟังก์ชัน

**ข้อกำหนด:** ทุกเมธอด public ต้องมี JSDoc

✅ **ตัวอย่างที่ดี:**

```javascript
/**
 * ลงทะเบียนผู้ใช้ใหม่ในระบบ
 *
 * @param {string} email - ที่อยู่อีเมลของผู้ใช้ (ต้องไม่ว่างเปล่า)
 * @param {string} password - รหัสผ่าน (ความยาว 8-100 อักขระ)
 * @param {string} firstName - ชื่อจริงของผู้ใช้
 * @param {string} lastName - นามสกุลของผู้ใช้
 *
 * @returns {Promise<{id: number, email: string}>} ข้อมูลผู้ใช้ที่สร้าง
 *
 * @throws {ValidationError} ถ้าอีเมลไม่ถูกต้องหรือรหัสผ่านสั้นเกินไป
 * @throws {ConflictError} ถ้าอีเมลนี้ลงทะเบียนแล้ว
 * @throws {DatabaseError} ถ้าเกิดข้อผิดพลาดฐานข้อมูล
 *
 * @example
 * const user = await userService.registerUser(
 *   'user@example.com',
 *   'securePassword123',
 *   'John',
 *   'Doe'
 * );
 * // Returns: { id: 1, email: 'user@example.com' }
 */
async registerUser(email, password, firstName, lastName) {
  // Implementation
}
```

---

## การจัดการข้อผิดพลาด

### Custom Error Classes

✅ **ตัวอย่างที่ดี:**

```javascript
// errors/ApplicationError.js
class ApplicationError extends Error {
  constructor(message, statusCode = 500, details = null) {
    super(message);
    this.name = this.constructor.name;
    this.statusCode = statusCode;
    this.details = details;
  }
}

class ValidationError extends ApplicationError {
  constructor(message, details) {
    super(message, 400, details);
  }
}

class NotFoundError extends ApplicationError {
  constructor(message, details) {
    super(message, 404, details);
  }
}

class ConflictError extends ApplicationError {
  constructor(message, details) {
    super(message, 409, details);
  }
}

class UnauthorizedError extends ApplicationError {
  constructor(message = "Unauthorized", details) {
    super(message, 401, details);
  }
}

module.exports = {
  ApplicationError,
  ValidationError,
  NotFoundError,
  ConflictError,
  UnauthorizedError,
};
```

### Pattern การจัดการข้อผิดพลาด

✅ **ตัวอย่างที่ดี:**

```javascript
// services/userService.js
async createUser(userData) {
  try {
    // Validate input
    this._validateUserData(userData);

    // Check if user exists
    const existingUser = await this.userRepository.findByEmail(userData.email);
    if (existingUser) {
      throw new ConflictError('Email already registered');
    }

    // Create user
    const user = await this.userRepository.create(userData);
    return user;

  } catch (error) {
    if (error instanceof ValidationError || error instanceof ConflictError) {
      // Re-throw application errors
      throw error;
    } else if (error instanceof DatabaseError) {
      logger.error('Database error creating user', { error, userData });
      throw new ApplicationError('Failed to create user', 500);
    } else {
      // Unexpected error
      logger.error('Unexpected error creating user', { error, userData });
      throw new ApplicationError('Unexpected error', 500);
    }
  }
}
```

---

## มาตรฐานการทดสอบ

### Test File Organization

```
tests/
├── unit/
│   └── services/
│       └── userService.test.js
└── integration/
    └── api/
        └── user.api.test.js
```

### Test Naming & Structure

✅ **ตัวอย่างที่ดี:**

```javascript
// tests/unit/services/userService.test.js
describe("UserService", () => {
  describe("registerUser", () => {
    test("should register a new user with valid data", async () => {
      // Arrange
      const userData = {
        email: "newuser@example.com",
        password: "securePassword123",
        firstName: "John",
        lastName: "Doe",
      };
      const mockRepository = {
        findByEmail: jest.fn().mockResolvedValue(null),
        create: jest.fn().mockResolvedValue({ id: 1, ...userData }),
      };
      const service = new UserService(mockRepository);

      // Act
      const result = await service.registerUser(
        userData.email,
        userData.password,
        userData.firstName,
        userData.lastName,
      );

      // Assert
      expect(result.id).toBe(1);
      expect(result.email).toBe(userData.email);
      expect(mockRepository.create).toHaveBeenCalledTimes(1);
    });

    test("should throw error if email already registered", async () => {
      // Arrange
      const existingUser = { id: 1, email: "existing@example.com" };
      const mockRepository = {
        findByEmail: jest.fn().mockResolvedValue(existingUser),
      };
      const service = new UserService(mockRepository);

      // Act & Assert
      await expect(
        service.registerUser(
          "existing@example.com",
          "password123",
          "John",
          "Doe",
        ),
      ).rejects.toThrow(ConflictError);
    });

    test("should throw error if password is too short", async () => {
      // Arrange
      const service = new UserService({});

      // Act & Assert
      await expect(
        service.registerUser("user@example.com", "short", "John", "Doe"),
      ).rejects.toThrow(ValidationError);
    });
  });
});
```

### Coverage Target

```javascript
// jest.config.js
module.exports = {
  testEnvironment: "node",
  collectCoverageFrom: ["src/**/*.js", "!src/config/**", "!src/index.js"],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
};
```

---

## ความปลอดภัย

### ข้อกำหนดความปลอดภัย

❌ **ห้าม:**

```javascript
// ❌ ห้าม: Secret ในโค้ด
const DATABASE_PASSWORD = "mySecretPassword123";

// ❌ ห้าม: SQL Injection
const query = `SELECT * FROM user WHERE email = '${email}'`;

// ❌ ห้าม: เก็บรหัสผ่านเป็น plaintext
user.password = password;

// ❌ ห้าม: ส่งข้อมูลที่ละเอียดอ่อนในบันทึก
logger.info("User password:", password);

// ❌ ห้าม: ไม่ตรวจสอบ input
app.post("/api/users", (req, res) => {
  const user = User.create(req.body);
});
```

✅ **ทำ:**

```javascript
// ✅ ทำ: ใช้ environment variables
const databasePassword = process.env.DATABASE_PASSWORD;

// ✅ ทำ: ใช้ parameterized queries
const users = await db.query("SELECT * FROM user WHERE email = ?", [email]);

// ✅ ทำ: Hash รหัสผ่าน
const hashedPassword = await bcrypt.hash(password, 10);
user.password = hashedPassword;

// ✅ ทำ: ไม่บันทึกข้อมูลที่ละเอียดอ่อน
logger.info("User registered", { userId: user.id, email: user.email });

// ✅ ทำ: ตรวจสอบและ validate input
const { error, value } = userValidator.validate(req.body);
if (error) throw new ValidationError(error.details[0].message);
```

---

## Git และ PR

### Branch Naming

```
feature/user-authentication
feature/shopping-cart
bugfix/fix-login-validation
hotfix/critical-security-patch
```

### Commit Message Format

```
feat(auth): add JWT token validation
- Validate JWT signature and expiration
- Return 401 if token is invalid
- Add unit tests for token validation

Closes #123
```

### PR Checklist

```markdown
## Description

เพิ่ม user registration API endpoint

## Related Issue

Closes #123

## Changes Made

- สร้าง POST /api/auth/register endpoint
- เพิ่ม email validation
- เพิ่ม password hashing
- เพิ่ม unit tests

## Testing

- ทดสอบ registration ด้วยอีเมลที่ถูกต้อง
- ทดสอบ registration ด้วยอีเมลไม่ถูกต้อง
- ทดสอบ registration ด้วยรหัสผ่านสั้น

## Checklist

- [x] Code follows style guidelines
- [x] Self-reviewed own code
- [x] Added comments in complex areas
- [x] Updated tests (100% coverage)
- [x] All tests pass locally
```

---

## พบข้อผิดพลาดทั่วไป

### 1. ชื่อที่ไม่ชัดเจน

❌ **ไม่ดี:**

```javascript
const d = new Date();
const u = getUser();
const s = calculateSomething();
```

✅ **ดี:**

```javascript
const createdAt = new Date();
const currentUser = getCurrentUser();
const totalPrice = calculateOrderTotal();
```

### 2. ฟังก์ชันที่ยาวเกินไป

❌ **ไม่ดี:**

```javascript
async function processOrder(orderId) {
  // 200+ บรรทัด
  // validation
  // payment processing
  // email sending
  // inventory update
}
```

✅ **ดี:**

```javascript
async function processOrder(orderId) {
  const order = await this.orderRepository.findById(orderId);
  await this.validateOrder(order);
  await this.processPayment(order);
  await this.sendConfirmationEmail(order);
  await this.updateInventory(order);
}
```

### 3. ไม่มี Error Handling

❌ **ไม่ดี:**

```javascript
const user = await userRepository.findById(userId);
user.email = newEmail;
await user.save();
```

✅ **ดี:**

```javascript
try {
  const user = await userRepository.findById(userId);
  if (!user) throw new NotFoundError("User not found");

  user.email = newEmail;
  await user.save();
} catch (error) {
  logger.error("Error updating user email", { error, userId });
  throw error;
}
```

### 4. SQL Injection

❌ **ไม่ดี:**

```javascript
const query = `SELECT * FROM user WHERE email = '${email}'`;
```

✅ **ดี:**

```javascript
const query = "SELECT * FROM user WHERE email = ?";
const users = await db.query(query, [email]);
```

---

## แหล่งอ้างอิง

### Documentation

- [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript)
- [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices)
- [Express.js Official Documentation](https://expressjs.com/)

### Tools

- **Linter:** ESLint
- **Formatter:** Prettier
- **Testing:** Jest
- **Database:** PostgreSQL, Sequelize ORM

### การสนับสนุนและการอัปเดต

**อีเมล:** tech-lead@example.com  
**Slack Channel:** #coding-standards  
**Git Wiki:** https://github.com/team/project/wiki

---

## การอนุมัติ

| บทบาท        | ชื่อ         | วันที่     | ลายเซ็น |
| ------------ | ------------ | ---------- | ------- |
| Tech Lead    | John Smith   | 2026-01-15 | ✅      |
| Scrum Master | Jane Doe     | 2026-01-15 | ✅      |
| Team Lead    | Mike Johnson | 2026-01-15 | ✅      |

---
