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 โดยตรง (จุดเด่นของผู้เรียน)
| NestJS | Spring (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 | ตัดสินสิทธิ์ ผ่าน/ไม่ผ่าน |
Interceptor | HandlerInterceptor / AOP @Around | ห่อก่อน-หลัง handler |
Pipe | argument resolver / converter | แปลง+ตรวจ argument |
Middleware | Servlet 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
| ด่าน | หน้าที่ | ตัวอย่างในโปรเจกต์ |
|---|---|---|
| Middleware | log/CORS เห็น request ดิบ ต้องเรียก next() | LoggerMiddleware |
| Guard | ตัดสินสิทธิ์ คืน true/false (throw 401/403) | JwtAuthGuard |
| Interceptor | ก่อน-หลัง handler + แตะ response (ใช้ RxJS) | LoggingInterceptor |
| Pipe | ตรวจ/แปลง input | ValidationPipe, ParseIntPipe |
| Exception Filter | จับ error → HTTP code | built-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(libclass-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-jwtstrategy) แต่ 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 +
ValidationPipeglobal - Auth ใช้ JWT + Guard + ownership scoping (where userId ทุก query)
Related
- Prisma — ORM ที่ใช้ใน PoC นี้
- Spring / Spring Boot — เทียบ concept ได้เกือบทุกอย่าง
- Dependency Injection — หัวใจร่วมกับ Spring
- IoC Container — กลไกที่ framework สร้าง+ฉีด dependency
- NestJS PoC Todo - Learning Notes — source สรุปการเรียน