NestJS

NestJS คือ backend framework ของฝั่ง Node.js ที่เขียนด้วย TypeScript เป็นหลัก จุดขายคือ “โครงสร้างชัดเจน มีระเบียบ” แทนที่จะปล่อยให้จัดโปรเจกต์เองเหมือน Express เปล่า ๆ — มันยืมแนวคิดจาก Spring / Angular มาเต็ม ๆ ทั้ง Module, Dependency Injection, Decorator และ lifecycle เป็นชั้น ๆ

สำหรับคนพื้น Java/Spring: NestJS คือ “Spring เวอร์ชัน Node” — concept แทบ map 1:1 (Dependency Injection, DI container, layered architecture) ต่างกันแค่ภาษาและ runtime

เบื้องหลัง NestJS รันบน Express (ค่า default) หรือสลับไป Fastify ได้ — Nest เป็นแค่ชั้น abstraction ที่ครอบ HTTP framework อีกที ดังนั้น “Middleware” ของ Nest จริง ๆ คือ Express middleware

เทียบ Java/Spring โดยตรง (จุดเด่นของผู้เรียน)

NestJSSpring (Java)หมายเหตุ
@Injectable()@Service / @Componentทำให้ class ถูก inject ได้
@Controller()@RestControllerชั้นรับ HTTP request
Constructor injection@Autowired (constructor)Nest แนะนำ constructor injection เป็นหลัก เหมือน Spring สมัยใหม่
@Module()@Configuration + component scanกล่องประกาศ providers/controllers/imports
DI container (built-in)IoC Containerหัวใจเหมือนกัน — framework สร้าง+ฉีด dependency ให้
ValidationPipe + DTO@Valid + Bean Validationตรวจ input ตาม annotation/decorator
Guard (CanActivate)Spring Security filterตัดสินสิทธิ์ ผ่าน/ไม่ผ่าน
InterceptorHandlerInterceptor / AOP @Aroundห่อก่อน-หลัง handler
Pipeargument resolver / converterแปลง+ตรวจ argument
MiddlewareServlet Filterด่านนอกสุด เห็น request ดิบ
ExceptionFilter@ControllerAdvice / @ExceptionHandlerจับ error → แปลงเป็น HTTP response
Prisma model@Entity (Hibernate/JPA)นิยามตาราง

ข้อต่างเชิงปรัชญา: Spring ใช้ reflection + classpath scanning ตอน runtime หนัก ๆ ส่วน Nest ใช้ decorator metadata (อาศัย reflect-metadata) ที่เบากว่า และเพราะเป็น TypeScript จึง compile เป็น JS ก่อนรัน — ไม่มี JVM แต่มี Node event loop (single-threaded + async)

แกนหลัก (Building Blocks)

  • Module = กล่องรวมโค้ดเรื่องเดียวกัน ประกาศ controllers, providers, imports, exports — หลักการคือ 1 เรื่อง = 1 โฟลเดอร์ = 1 module
  • Controller = ชั้นรับ request (พนักงานต้อนรับ) แต่ละเมธอด = handler ของ 1 route ควรบาง แค่รับ-ส่ง
  • Service / Provider = logic จริง คุยกับ DB (พ่อครัว) ควรหนา — เป็น @Injectable() ที่ inject ได้
  • DI = ขอ dependency ผ่าน constructor → Nest สร้างให้เอง: constructor(private todos: TodosService) {}
  • Global module = ตั้ง @Global() แล้ว provider ใช้ได้ทุก module โดยไม่ต้อง import ซ้ำ (เช่น PrismaModule, ConfigModule) — เทียบได้กับ bean scope แบบ singleton ทั้ง context ของ Spring

Request Lifecycle — 4 ด่าน (เรียงลำดับสำคัญ)

request → Middleware → Guard → Interceptor(ก่อน) → Pipe → Handler → Interceptor(หลัง) → response
                                                                  ↘ (ถ้า throw) → Exception Filter
ด่านหน้าที่ตัวอย่างในโปรเจกต์
Middlewarelog/CORS เห็น request ดิบ ต้องเรียก next()LoggerMiddleware
Guardตัดสินสิทธิ์ คืน true/false (throw 401/403)JwtAuthGuard
Interceptorก่อน-หลัง handler + แตะ response (ใช้ RxJS)LoggingInterceptor
Pipeตรวจ/แปลง inputValidationPipe, ParseIntPipe
Exception Filterจับ error → HTTP codebuilt-in (NotFoundException ฯลฯ)

ลำดับนี้คือสิ่งที่ต้องจำให้ขึ้นใจ — เป็นแกนของ Nest ทั้งหมด Guard มาก่อน Pipe เสมอ (ตรวจสิทธิ์ก่อนเปลือง CPU validate)

Decorator ที่ใช้บ่อย

  • โครงสร้าง: @Module @Controller @Injectable
  • route: @Get @Post @Patch @Put @Delete
  • ดึงข้อมูล: @Body() @Param() @Query()
  • auth: @UseGuards() + custom @UserId() (สร้างด้วย createParamDecorator)
  • เอกสาร: @ApiTags @ApiProperty (Swagger)

Validation

  • กฎเขียนเป็น decorator บน DTO: @IsString @IsNotEmpty @MaxLength @IsEmail @IsOptional (lib class-validator)
  • เปิด ValidationPipe แบบ global พร้อม 3 ออปชันสำคัญ:
    • whitelist — ตัด field ที่ไม่ได้ประกาศใน DTO ทิ้ง
    • forbidNonWhitelisted — เจอ field เกิน → ตอบ 400 ทันที
    • transform — แปลง payload เป็น instance ของ DTO class (+ แปลง type)

Auth (JWT) 🔐

register: เช็ค email ซ้ำ(409) → bcrypt.hash → เก็บ user → คืน user (ตัด password ออก)
login:    หา user → bcrypt.compare → เซ็น JWT {sub,email} → คืน access_token
guard:    อ่าน "Authorization: Bearer <token>" → jwt.verify → req.user=payload → ผ่าน/401
ownership: ทุก query ใส่ where:{userId} → findFirst{id,userId} → ของคนอื่น = 404
  • JWT = header.payload.signature — payload ถอดอ่านได้ (base64) แต่ ปลอมไม่ได้ เพราะ signature เซ็นด้วย JWT_SECRET (เก็บใน .env)
  • sub = claim มาตรฐาน = id เจ้าของ token
  • ownership scoping = ใส่ userId ในทุก query ทำให้แต่ละคนเห็นเฉพาะของตัวเอง (ของคนอื่นตอบ 404 ไม่ใช่ 403 — กันรู้ว่า id มีอยู่จริง)
  • Production จริง ๆ นิยมใช้ Passport.js (@nestjs/passport + passport-jwt strategy) แต่ PoC นี้เขียน Guard เอง เพื่อเข้าใจกลไก

Testing

  • Unit test (*.spec.ts, Jest): ทดสอบ Service ด้วย mock Prisma (ไม่แตะ DB จริง) ใช้รูปแบบ AAA (Arrange → Act → Assert) ผ่าน TestingModule (DI container จำลอง)
  • API smoke test (scripts/test-api.sh): ยิง HTTP จริง 23 เคส ครอบคลุม auth, guard, CRUD, validation, isolation

Stack ที่ใช้ใน PoC

NestJS 11 + Prisma 7 + SQLite + class-validator + JWT(bcrypt) + Swagger + Jest

Key Points

  • NestJS = “Spring ของฝั่ง Node” — Module/DI/Decorator/layered map กับ Spring เกือบ 1:1
  • รันบน Express (default) หรือ Fastify ได้ — Nest เป็นชั้น abstraction
  • หัวใจที่ต้องจำ: 4 ด่าน lifecycle (Middleware → Guard → Interceptor → Pipe → Handler)
  • Controller บาง / Service หนา / DI ผ่าน constructor
  • Validation อยู่บน DTO ด้วย decorator + ValidationPipe global
  • Auth ใช้ JWT + Guard + ownership scoping (where userId ทุก query)