# **BACKEND DEVELOPMENT** ## **Backend Development Plan (Phase 0)** และ **Requirements** เราจะสร้างโปรเจกต์ชื่อ `lcbp3-backend` โดยใช้ **NestJS** และจัดโครงสร้างแบบ **Domain-Driven Design** ตั้งแต่เริ่มต้นเพื่อให้รองรับการขยายตัวตามแผนงานครับ นี่คือขั้นตอนและคำสั่งในการสร้างโปรเจกต์ครับ: ### 1. ติดตั้ง Nest CLI และสร้างโปรเจกต์ รันคำสั่งต่อไปนี้ใน Terminal ครับ (แนะนำให้ใช้ `pnpm` ตามแนวทาง FullStackJS Guidelines): ```bash # 1. ติดตั้ง Nest CLI ทั่วโลก (ถ้ายังไม่ได้ติดตั้ง) npm install -g @nestjs/cli # 2. สร้างโปรเจกต์ใหม่ชื่อ lcbp3-backend nest new backend # 💡 ระบบจะถามหา Package Manager: # ให้เลือก "pnpm" (เพื่อให้ตรงกับ Frontend และแผนงาน) ``` ### 2. ปรับโครงสร้างโฟลเดอร์ (Domain-Driven Structure) หลังจากสร้างโปรเจกต์เสร็จแล้ว เราจะปรับโครงสร้าง `src` ให้ตรงกับ **Backend Plan หัวข้อ 3.10** ครับ รันคำสั่งเหล่านี้ใน Terminal (ภายในโฟลเดอร์ `lcbp3-backend`): ```bash # เข้าสู่โฟลเดอร์โปรเจกต์ cd backend # สร้างโครงสร้างโฟลเดอร์หลัก mkdir -p src/common/auth mkdir -p src/common/config mkdir -p src/common/decorators mkdir -p src/common/entities mkdir -p src/common/exceptions mkdir -p src/common/file-storage mkdir -p src/common/guards mkdir -p src/common/interceptors mkdir -p src/common/resilience mkdir -p src/common/security mkdir -p src/common/idempotency mkdir -p src/common/maintenance # สร้างโฟลเดอร์สำหรับ Modules ตาม Domain mkdir -p src/modules/user mkdir -p src/modules/project mkdir -p src/modules/master mkdir -p src/modules/correspondence mkdir -p src/modules/rfa mkdir -p src/modules/drawing mkdir -p src/modules/circulation mkdir -p src/modules/transmittal mkdir -p src/modules/search mkdir -p src/modules/monitoring mkdir -p src/modules/workflow-engine mkdir -p src/modules/document-numbering mkdir -p src/modules/notification mkdir -p src/modules/file-storage # สร้างโฟลเดอร์ Database mkdir -p src/database/migrations mkdir -p src/database/seeds ``` ### 3. ตรวจสอบและตั้งค่า TypeScript (Strict Mode) อ้างอิงจาก **FullStackJS Guidelines ข้อ 2.1**, เราต้องเปิดใช้งาน Strict Mode เพื่อความปลอดภัยของ Type ครับ เปิดไฟล์ `tsconfig.json` และตรวจสอบค่า config ดังนี้: ```json { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, /* Strict Type-Checking Options */ "strict": true, // บังคับใช้ Strict Mode "noImplicitAny": true, // ห้ามใช้ Any โดยไม่จำเป็น "strictNullChecks": true, // ตรวจสอบค่า Null อย่างเคร่งครัด "strictBindCallApply": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true } } ``` ### 4. การตั้งค่า Environment (Docker Support) ตาม **Requirements ข้อ 2.1 และ 2.2**, เราจะไม่ใช้ `.env` ในการ deploy จริงบน QNAP แต่จะใช้ Environment Variables ผ่าน `docker-compose` อย่างไรก็ตาม สำหรับ **Development** ให้สร้างไฟล์ `.env` ไว้สำหรับการรัน Local (และอย่าลืมใส่ใน `.gitignore`) สร้างไฟล์ `.env` ที่ root project: ```env # Database Configuration (สำหรับการ Dev บนเครื่อง Local) DB_HOST=localhost DB_PORT=3306 DB_USERNAME=root DB_PASSWORD=password123 DB_DATABASE=lcbp3_dms # Application PORT=3000 NODE_ENV=development # Security JWT_SECRET=eebc122aa65adde8c76c6a0847d9649b2b67a06db1504693e6c912e51499b76e ``` ### ขั้นตอนต่อไป (Next Steps) ตอนนี้โครงสร้างพื้นฐานพร้อมแล้วครับ ขั้นตอนต่อไปตาม **Phase 0** คือ: 1. **Secure Configuration Setup:** ติดตั้ง `@nestjs/config` และ `joi` เพื่อ validate ตัวแปร environment 2. **Database Connection:** ติดตั้ง `typeorm`, `@nestjs/typeorm` และ `mysql2` (สำหรับ MariaDB) ต้องการให้ผมพาทำส่วน **Configuration Setup (T0.1)** ต่อเลยไหมครับ? ## การติดตั้ง **pnpm** ### 1. ติดตั้งผ่าน npm (วิธีแนะนำ) เปิด Terminal (หรือ Command Prompt / PowerShell) แล้วรันคำสั่ง: ```bash npm install -g pnpm ``` > **คำอธิบาย:** `-g` หมายถึง global install คือติดตั้งลงในเครื่องเพื่อให้เรียกใช้คำสั่ง `pnpm` ได้จากทุกโฟลเดอร์ครับ ### 2. ตรวจสอบการติดตั้ง เมื่อติดตั้งเสร็จแล้ว ให้เช็คเวอร์ชันเพื่อความชัวร์ครับ: ```bash pnpm --version ``` (ควรจะขึ้นเป็นตัวเลขเวอร์ชัน เช่น `9.x.x`) ----- ### ทางเลือก: ติดตั้งผ่าน Corepack (สำหรับ Node.js เวอร์ชันใหม่) หากคุณใช้ Node.js เวอร์ชัน 16.13 ขึ้นไป สามารถเปิดใช้งานฟีเจอร์ **Corepack** ที่แถมมากับ Node.js ได้เลย โดยไม่ต้องลงผ่าน npm ครับ: ```bash corepack enable corepack prepare pnpm@latest --activate ``` ----- ### เมื่อติดตั้งเสร็จแล้ว คุณสามารถใช้ `pnpm` แทน `npm` ได้เลยครับ เช่น: * **ติดตั้ง Nest CLI (ถ้ายังไม่ได้ลง):** `pnpm add -g @nestjs/cli` * **สร้างโปรเจกต์ NestJS:** `nest new lcbp3-backend` (แล้วเลือก pnpm ตอนระบบถาม) * **รันโปรเจกต์:** `pnpm start:dev` * **ติดตั้ง Library เพิ่ม:** `pnpm add [package-name]` (เช่น `pnpm add typeorm`) ติดตั้งเสร็จแล้ว แจ้งผมได้เลยนะครับ จะได้ไปต่อที่ขั้นตอน **Config Database** กันครับ ## การตั้งค่า MariaDB บน `localhost` เพื่อใช้กับ NestJS ตามแผนงาน **Backend Plan T0.3** และ **FullStackJS Guidelines 3.2** เราต้องทำ 2 ส่วนคือ **เตรียม Database Server (Docker)** และ **เชื่อมต่อ NestJS (TypeORM)** ครับ ### 1. ติดตั้ง Libraries ที่จำเป็น ก่อนอื่นต้องติดตั้ง Module สำหรับ TypeORM, MariaDB Driver และ Config Module (เพื่ออ่านค่าจาก .env) ครับ รันคำสั่งนี้ใน Terminal: ```bash pnpm add @nestjs/typeorm typeorm mysql2 @nestjs/config ``` > **หมายเหตุ:** เราใช้ `mysql2` driver เพราะ MariaDB เข้ากันได้กับ MySQL Protocol และ `mysql2` มีประสิทธิภาพสูงครับ ### 2. สร้าง MariaDB Server ด้วย Docker Compose เพื่อให้มั่นใจว่า Environment ตรงกับ Production (ตามข้อกำหนด \*\* Requirements 2.1\*\* ที่ใช้ Container) เราจะรัน MariaDB ผ่าน Docker ครับ สร้างไฟล์ `docker-compose.yml` ที่ root ของโปรเจกต์ (ถ้ายังไม่มี): ```yaml version: '3.8' services: mariadb: image: mariadb:10.11 container_name: lcbp3-db-local restart: always environment: MYSQL_ROOT_PASSWORD: password123 MYSQL_DATABASE: lcbp3_dms MYSQL_USER: admin MYSQL_PASSWORD: password123 ports: - '3306:3306' volumes: - db_data:/var/lib/mysql networks: - lcbp3-net # Optional: phpMyAdmin สำหรับจัดการ DB ง่ายๆ pma: image: phpmyadmin/phpmyadmin container_name: lcbp3-pma-local environment: PMA_HOST: mariadb ports: - '8080:80' depends_on: - mariadb networks: - lcbp3-net volumes: db_data: networks: lcbp3-net: driver: bridge ``` จากนั้นรันคำสั่งเพื่อเริ่ม Database: ```bash docker-compose up -d ``` ### 3. ตั้งค่าการเชื่อมต่อใน `app.module.ts` เราจะแก้ไขไฟล์ `src/app.module.ts` ให้เชื่อมต่อกับ Database โดยอ่านค่าจาก `.env` ผ่าน `ConfigService` เพื่อความปลอดภัยและยืดหยุ่นครับ ```typescript // src/app.module.ts import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [ // 1. Load Config Module ConfigModule.forRoot({ isGlobal: true, // ให้เรียกใช้ได้ทุกที่โดยไม่ต้อง import ใหม่ envFilePath: '.env', // อ่านค่าจากไฟล์ .env }), // 2. Setup TypeORM Connection (Async เพื่อรออ่าน Config ก่อน) TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ type: 'mariadb', // หรือ 'mysql' ก็ได้เพราะใช้ driver เดียวกัน host: configService.get('DB_HOST'), port: configService.get('DB_PORT'), username: configService.get('DB_USERNAME'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_DATABASE'), // Auto Load Entities: โหลด Entity ทั้งหมดที่อยู่ในโปรเจกต์อัตโนมัติ autoLoadEntities: true, // Synchronize: true เฉพาะ Dev environment (ห้ามใช้ใน Prod) synchronize: configService.get('NODE_ENV') === 'development', // Logging: เปิดดู Query SQL ตอน Dev logging: configService.get('NODE_ENV') === 'development', }), inject: [ConfigService], }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ``` ### 4. ตรวจสอบไฟล์ `.env` ตรวจสอบไฟล์ `.env` ที่เราสร้างไว้ก่อนหน้านี้ว่าข้อมูลตรงกับ `docker-compose.yml` ไหมครับ ```env # Database Configuration DB_HOST=localhost DB_PORT=3306 DB_USERNAME=root # หรือใช้ user: admin / password: password123 ตามที่ตั้งใน docker-compose ก็ได้ # แต่ถ้า connect จาก host (เครื่องเรา) ใช้ root ก็สะดวกดีครับ DB_PASSWORD=password123 DB_DATABASE=lcbp3_dms # App Environment NODE_ENV=development PORT=3000 ``` ### 5. ทดสอบรันโปรเจกต์ ลองรัน NestJS ดูครับว่าเชื่อมต่อได้ไหม: ```bash pnpm start:dev ``` ถ้า Terminal ขึ้นข้อความสีเขียวๆ ว่า `[NestApplication] Nest application successfully started` และไม่มี Error เกี่ยวกับ TypeORM แสดงว่าเชื่อมต่อ MariaDB บน Localhost สำเร็จแล้วครับ\! 🎉 ----- **ขั้นตอนถัดไป:** เราควรสร้าง **Base Entity** (T1.1) และ **Users Entity** (T1.3) เพื่อเริ่มทำระบบ Login ไหมครับ? ## การสร้าง `JWT_SECRET` (JSON Web Token Secret) ที่มีความปลอดภัยสูงเป็นสิ่งสำคัญมากสำหรับระบบ Authentication เพราะหาก Secret นี้หลุดหรือเดาง่าย ผู้ไม่หวังดีอาจปลอมแปลง Token เพื่อเข้าสู่ระบบในฐานะใครก็ได้ (เช่น Superadmin) ในแผนพัฒนา **Backend Plan Phase 0 (T0.1)** และ **FullStackJS Guidelines** ระบุว่าต้องจัดการ Secrets อย่างปลอดภัย นี่คือวิธีสร้าง `JWT_SECRET` ที่มีความซับซ้อนและปลอดภัย (High Entropy) ครับ: ### วิธีที่ 1: ใช้ OpenSSL (แนะนำสำหรับ Production) วิธีนี้เป็นมาตรฐานและปลอดภัยที่สุด หากคุณใช้ Linux, macOS หรือ Git Bash บน Windows ให้เปิด Terminal แล้วพิมพ์คำสั่ง: ```bash openssl rand -base64 64 ``` *ผลลัพธ์จะได้ String ยาวๆ ที่อ่านไม่รู้เรื่อง เช่น:* `h78/sT5...xYz==` ### วิธีที่ 2: ใช้ Node.js Crypto (ง่ายสำหรับ Developer) เนื่องจากเราพัฒนาด้วย Node.js/NestJS คุณสามารถใช้คำสั่ง Node เพื่อสร้าง Key ได้เลย ไม่ต้องติดตั้งอะไรเพิ่มครับ รันคำสั่งนี้ใน Terminal: ```bash node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` *ผลลัพธ์จะได้ Hex String ยาว 64 ตัวอักษร* ### วิธีที่ 3: ใช้ Password Manager Generator หากใช้ 1Password, LastPass หรือ Bitwarden สามารถใช้ฟีเจอร์ "Generate Password" โดยตั้งค่าความยาว 64 ตัวอักษร (รวมตัวเลขและสัญลักษณ์) ก็ใช้ได้เช่นกันครับ ----- ### ⚙️ การนำไปใช้งานในโปรเจกต์ เมื่อได้ Secret Key มาแล้ว ให้นำไปใส่ในไฟล์ `.env` ของคุณ (และ **ห้าม** Commit ไฟล์นี้ขึ้น Git เด็ดขาด ตามข้อกำหนด): **ไฟล์: `.env`** ```env # Security # แทนที่ค่าเดิมด้วยค่าที่ Gen ได้ใหม่ JWT_SECRET=d4a1b2... (ค่าที่คุณเพิ่งสร้าง) JWT_EXPIRATION=8h ``` **ข้อควรระวัง:** 1. **ความยาว:** ควรยาวอย่างน้อย 32 bytes (256 bits) เพื่อป้องกัน Brute-force 2. **Environment:** ควรแยก Secret ระหว่างเครื่อง `Development` และ `Production` ไม่ควรใช้ Key เดียวกัน คุณต้องการให้ผมช่วยตั้งค่า `ConfigModule` ใน NestJS เพื่ออ่านค่า `JWT_SECRET` นี้อย่างปลอดภัยพร้อม Validate ว่าห้ามเป็นค่าว่างไหมครับ? ## ได้เลยครับ เรามาลุยงาน **T0.1 Secure Configuration Setup** ตามแผน Backend Plan กันต่อครับ เป้าหมายของ Task นี้คือการทำให้ระบบอ่านค่า Environment Variables ได้อย่างถูกต้อง และ **"ต้องไม่ Start"** ถ้าค่า Config สำคัญๆ (เช่น Database Password, JWT Secret) หายไป หรือไม่ถูกต้อง เพื่อความปลอดภัยครับ ### 1. ติดตั้ง Library ที่จำเป็น เราจะใช้ `@nestjs/config` สำหรับจัดการ Config และ `joi` สำหรับตรวจสอบความถูกต้อง (Validation) ครับ รันคำสั่งนี้ใน Terminal: ```bash pnpm add @nestjs/config joi ``` ### 2. สร้าง Validation Schema เราจะแยก Logic การตรวจสอบออกมาเป็นไฟล์เฉพาะ เพื่อให้โค้ดเป็นระเบียบครับ สร้างไฟล์ใหม่: `src/common/config/env.validation.ts` ```typescript // File: src/common/config/env.validation.ts import Joi from 'joi'; // สร้าง Schema สำหรับตรวจสอบค่า Environment Variables export const envValidationSchema = Joi.object({ // 1. Application Environment NODE_ENV: Joi.string() .valid('development', 'production', 'test', 'provision') .default('development'), PORT: Joi.number().default(3000), // 2. Database Configuration (MariaDB) // ห้ามเป็นค่าว่าง (required) DB_HOST: Joi.string().required(), DB_PORT: Joi.number().default(3306), DB_USERNAME: Joi.string().required(), DB_PASSWORD: Joi.string().required(), DB_DATABASE: Joi.string().required(), // 3. Security (JWT) // ต้องมีค่า และควรยาวพอ (ตรวจสอบความยาวได้ถ้าระบุ min) JWT_SECRET: Joi.string().required().min(32).message('JWT_SECRET must be at least 32 characters long for security.'), JWT_EXPIRATION: Joi.string().default('8h'), }); ``` ### 3. อัปเดต `AppModule` ให้ใช้ Validation กลับไปแก้ไขไฟล์ `src/app.module.ts` เพื่อนำ Schema ที่เราสร้างมาใช้งานครับ ```typescript // File: src/app.module.ts import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { envValidationSchema } from './common/config/env.validation.js'; // สังเกต .js สำหรับ ESM @Module({ imports: [ // 1. Setup Config Module พร้อม Validation ConfigModule.forRoot({ isGlobal: true, // เรียกใช้ได้ทั่วทั้ง App ไม่ต้อง import ซ้ำ envFilePath: '.env', // อ่านไฟล์ .env (สำหรับ Dev) validationSchema: envValidationSchema, // ใช้ Schema ที่เราสร้างเพื่อตรวจสอบ validationOptions: { // ถ้ามีค่าไหนไม่ผ่าน Validation ให้ Error และหยุดทำงานทันที abortEarly: true, }, }), // 2. Setup TypeORM (MariaDB) TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => ({ type: 'mariadb', host: configService.get('DB_HOST'), port: configService.get('DB_PORT'), username: configService.get('DB_USERNAME'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_DATABASE'), autoLoadEntities: true, // synchronize: true เฉพาะตอน Dev เท่านั้น ห้ามใช้บน Prod synchronize: configService.get('NODE_ENV') === 'development', }), }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ``` ### 4\. สร้างไฟล์ Template สำหรับ Docker Override (ตามแผน T0.1) ตามแผนงาน เราต้องเตรียมไฟล์ `docker-compose.override.yml.example` เพื่อให้นักพัฒนาคนอื่นรู้ว่าต้อง Inject Secrets อย่างไร โดยไม่เปิดเผยความลับใน `docker-compose.yml` หลัก สร้างไฟล์: `docker-compose.override.yml.example` ที่ root project: ```yaml version: '3.8' services: # Override ค่า Config ของ Service Backend (เมื่อเราสร้าง Container Backend ในอนาคต) backend: environment: # ใส่ Secrets จริงในไฟล์ override นี้ (และห้าม commit ไฟล์นี้) - DB_PASSWORD=secret_password_here - JWT_SECRET=very_long_and_secure_secret_here - NODE_ENV=development # Override สำหรับ Database (Local Dev) mariadb: environment: - MYSQL_ROOT_PASSWORD=secret_password_here - MYSQL_PASSWORD=secret_password_here ``` ### 5\. ทดสอบความปลอดภัย (Validation Check) ลองทดสอบว่าระบบ Validation ทำงานถูกต้องหรือไม่ โดยการ **ลบ** หรือ **เปลี่ยนชื่อ** ตัวแปรสำคัญในไฟล์ `.env` (เช่น ลบ `JWT_SECRET`) แล้วลองรัน: ```bash pnpm start:dev ``` **ผลลัพธ์ที่ควรได้:** App **ต้อง Crash** และแสดง Error Message ชัดเจน เช่น: `Config validation error: "JWT_SECRET" is required` ถ้าขึ้นแบบนี้แสดงว่าระบบ **Secure Configuration** ของเราทำงานถูกต้องตามแผน T0.1 แล้วครับ\! 🎉 ----- **ขั้นตอนถัดไป:** เราจะไปต่อที่ **T0.2 Redis & Queue Infrastructure** เพื่อเตรียมระบบ Caching และ Background Job ไหมครับ?