NestJS ตั้งแต่ศูนย์: ระบบจัดการหนังสือ
ตั้งแต่ติดตั้งเครื่องมือ สร้างโฟลเดอร์ รัน npm จนยิง API ได้ พร้อมลิงก์เรียนรู้ CRUD และ Swagger แยกภาษาไทยและภาษาอังกฤษ
วิธีเริ่มต้น
1ติดตั้ง Node.js
ดาวน์โหลดเวอร์ชัน LTS จาก nodejs.org (npm จะติดมาด้วย) แล้วเช็คใน terminal
node -v npm -v
2ติดตั้ง Nest CLI
เครื่องมือบรรทัดคำสั่งของ NestJS ติดตั้งครั้งเดียวใช้ได้ทุกโปรเจกต์
npm i -g @nestjs/cli nest --version
ถ้าขึ้น nest is not recognized ให้ปิดแล้วเปิด terminal ใหม่ ส่วน Windows PowerShell ที่ติด execution policy ให้ใช้ Command Prompt แทน
3สร้างโฟลเดอร์และโปรเจกต์
เลือกที่เก็บงาน แล้วให้ Nest สร้างโฟลเดอร์โปรเจกต์ให้เอง (ไม่ต้อง mkdir ชื่อโปรเจกต์) เลือก npm เมื่อถูกถาม
mkdir projects && cd projects nest new library-api cd library-api
4รันครั้งแรก
npm run start:dev
เปิด http://localhost:3000 จะเห็น Hello World! โหมดนี้รีสตาร์ทเองทุกครั้งที่เซฟไฟล์ (หยุดด้วย Ctrl+C)
5ติดตั้งแพ็กเกจเพิ่ม
npm install class-validator class-transformer @nestjs/swagger
สองตัวแรกใช้ตรวจความถูกต้องของข้อมูล (ตอบ 400) ตัวที่สามสร้างหน้าเอกสาร API ที่ /docs
6สร้างโครง CRUD
nest g resource books
เลือก REST API และตอบ Yes ตอนถามเรื่อง CRUD entry points จะได้ module, controller, service, DTO และ entity ครบ จากนั้นนำโค้ดของ books.controller.ts, books.service.ts, book.dto.ts ที่ผมให้ไว้ก่อนหน้านี้มาวางทับ
ตัวสร้างอัตโนมัติใช้ PATCH สำหรับแก้ไข แต่โจทย์ของเราใช้ PUT จึงต้องแก้ใน controller ตามโค้ดที่ให้ไว้
| ไฟล์ | หน้าที่ |
|---|---|
| books.controller.ts | กำหนด route GET/POST/PUT/DELETE |
| books.service.ts | ตรรกะและข้อมูลจำลอง |
| dto/book.dto.ts | รูปแบบข้อมูลและ validation |
7เปิด Validation และ Swagger ใน main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
const cfg = new DocumentBuilder().setTitle('Library API').build();
SwaggerModule.setup('docs', app, SwaggerModule.createDocument(app, cfg));
await app.listen(3000);
}
bootstrap();8ทดสอบ
เปิด http://localhost:3000/docs แล้วกด Try it out หรือใช้ curl
curl http://localhost:3000/books curl -i http://localhost:3000/books/999 # ได้ 404
| คำสั่ง | ใช้ทำอะไร |
|---|---|
| npm run start:dev | รันตอนพัฒนา (auto reload) |
| npm run build | คอมไพล์เป็นโฟลเดอร์ dist |
| npm run start:prod | รันจากโค้ดที่ build แล้ว |
| npm run test | รันเทสต์ |
ปัญหาที่พบบ่อย
Cannot find module './dto/book.dto' แปลว่าไฟล์ไม่ได้อยู่ที่ src/books/dto/book.dto.ts หรือชื่อสะกดไม่ตรง
EADDRINUSE แปลว่าพอร์ต 3000 ถูกใช้อยู่ ให้ปิดโปรเจกต์เดิมหรือเปลี่ยนพอร์ตใน listen()
การใช้ Swagger
Swagger ทำอะไรได้
สร้างหน้าเอกสาร API ให้อัตโนมัติจากโค้ด เปิดที่ /docs เห็นทุก endpoint และกด Try it out เพื่อยิง API ทดสอบได้เลย (ตั้งค่าใน main.ts ตามขั้นที่ 7 ด้านบน)
DTO ใส่ @ApiProperty({ example: ... }) เพื่อให้ช่อง Request body มีตัวอย่างข้อมูล
Controller ใส่ decorator เหล่านี้เพื่อบอกสถานะที่ตอบกลับ
import { ApiTags, ApiOperation, ApiOkResponse,
ApiCreatedResponse, ApiNotFoundResponse } from '@nestjs/swagger';
@ApiTags('books') // จัดกลุ่มหัวข้อ
@Controller('books')
export class BooksController {
@Get(':id')
@ApiOperation({ summary: 'ดึงหนังสือเล่มเดียว' })
@ApiOkResponse({ description: 'พบหนังสือ (200)' })
@ApiNotFoundResponse({ description: 'ไม่พบหนังสือ (404)' })
findOne(@Param('id', ParseIntPipe) id: number) { ... }
@Post()
@ApiCreatedResponse({ description: 'สร้างสำเร็จ (201)' })
create(@Body() dto: BookDto) { ... }
}ถ้าอยากไม่ต้องเขียน @ApiProperty ทุกฟิลด์ ให้เปิด CLI plugin โดยเพิ่ม @nestjs/swagger ใน nest-cli.json (ดูลิงก์หัวข้อ “เรื่อง Swagger” ท้ายหน้า)
🔁 Gen โค้ดจาก OpenAPI / Postman (ไม่ต้องเริ่มจาก 0)
อาจารย์ให้ Swagger (OpenAPI) มาแล้ว ใช้ตัว generator สร้างโครง controller และ DTO ให้ก่อน จากนั้นค่อยเติมตรรกะใน service คำสั่งด้านล่างรวบรวมจากเอกสารของแต่ละเครื่องมือ ผมยังไม่ได้ลองรันเอง ถ้า error ให้เทียบกับ README ของแพ็กเกจนั้น (ลิงก์ท้ายหัวข้อ)
0ดูก่อนว่าได้อะไรมาจากอาจารย์
| สิ่งที่ได้มา | ทำอะไรต่อ |
|---|---|
| ไฟล์ openapi.yaml / openapi.json (หรือ swagger.json) | ไปขั้น 2 ได้เลย |
| Postman collection (.json) | แปลงเป็น OpenAPI ก่อนที่ขั้น 1 แล้วไปขั้น 2 |
| ลิงก์หน้า Swagger UI | ถ้าเป็น NestJS ลองเปิด /docs-json ต่อท้ายพาธ Swagger (เช่น http://localhost:3000/docs-json) แล้วเซฟเป็นไฟล์ ถ้าเป็นเซิร์ฟเวอร์อื่นพาธอาจต่างออกไป ดูลิงก์ใต้ชื่อเอกสารในหน้า Swagger UI |
1แปลง Postman collection เป็น OpenAPI
ใช้แพ็กเกจ postman-to-openapi (รองรับ collection v2.0/v2.1 แปลงเป็น OpenAPI 3.0)
npm i -g postman-to-openapi p2o ./collection.json -f ./openapi.yml
Postman collection คือตัวอย่าง request ไม่ใช่สัญญา API ที่สมบูรณ์ schema ที่ได้เป็นการเดาจากตัวอย่าง ต้องเปิดไฟล์ตรวจเองว่าชนิดข้อมูล field ที่ optional และ response ผิดพลาด (400/404) ครบหรือยัง
2Gen โครง NestJS จาก OpenAPI
ตัวเลือก A: @snow-tzu/nest-openapi-code-generator แบบ contract-first สร้าง controller, DTO (มี class-validator และ @ApiProperty ให้) จาก OpenAPI 3.1
cd library-api npm install @snow-tzu/nest-openapi-code-generator mkdir specs # วางไฟล์ spec ไว้ที่ specs/books.openapi.yaml npx openapi-generate --specs ./specs --output ./src/generated
ตัวสร้างมีโหมด --watch ที่ gen ใหม่เมื่อไฟล์ spec เปลี่ยน และตั้งค่าเพิ่มผ่าน openapi.config.ts ได้ (specsDir, outputDir, generateControllers, generateDtos) ชื่อคลาสที่ได้อิงจากชื่อไฟล์ spec จึงควรตั้งชื่อเป็น books.openapi.yaml
ตัวเลือก B: @fresha/openapi-codegen สร้าง module, controller, action และ DTO ตามแนวทางของ NestJS
npm install @fresha/openapi-codegen npx fresha-openapi-codegen server-nestjs --input ./openapi.yaml --output .
ข้อควรระวัง: generator typescript-nestjs ของ OpenAPI Generator (openapi-generator-cli) เป็น ฝั่ง client และยังเป็น experimental ไม่ใช่ตัวสร้างโครง server ดังนั้นสำหรับ backend ให้ใช้ตัวเลือก A หรือ B
3ต่อโค้ดที่ gen เข้ากับโปรเจกต์
โครงไฟล์ที่ได้ขึ้นกับเครื่องมือและเวอร์ชัน เปิดโฟลเดอร์ผลลัพธ์ดูก่อน แล้วทำตามลำดับนี้
| ลำดับ | สิ่งที่ทำ |
|---|---|
| 1 | เก็บโค้ดที่ gen ไว้ในโฟลเดอร์แยก เช่น src/generated และพยายามไม่แก้ตรงๆ เพราะ gen ใหม่อาจเขียนทับ |
| 2 | นำ module หรือ controller ที่ได้ไป import ใน app.module.ts |
| 3 | เติมตรรกะที่เป็นข้อมูลจำลอง (หรือต่อฐานข้อมูล) ใน service โดยนำโค้ดจาก books.service.ts ด้านล่างไปปรับ |
| 4 | รัน npm run start:dev เปิด /docs-json เทียบกับ spec ของอาจารย์ ว่า path, method, ชื่อ field และ status code ตรงกัน |
4ตัวอย่าง spec ของระบบ books
ถ้ายังไม่มีไฟล์จากอาจารย์ ใช้ไฟล์นี้ทดลอง generator ได้ (ตรงกับ endpoint และ field ในโค้ดด้านล่าง) วางเป็น specs/books.openapi.yaml
5Gen โค้ดฝั่ง Next.js จาก spec เดียวกัน
ใช้ spec ตัวเดิมสร้างฟังก์ชันเรียก API พร้อมชนิดข้อมูล แทนการเขียน lib/api.ts เอง
cd library-web # orval: สร้าง client พร้อมชนิดข้อมูลจาก OpenAPI v3 หรือ Swagger v2 npm i -D orval npx orval --input ../library-api/specs/books.openapi.yaml --output ./lib/generated/books.ts # หรือ openapi-typescript: สร้างเฉพาะชนิดข้อมูล TypeScript npx openapi-typescript ../library-api/specs/books.openapi.yaml -o ./lib/schema.d.ts
อีกทางคือ OpenAPI Generator (ต้องมี Java) เช่น npx @openapitools/openapi-generator-cli generate -i openapi.yaml -g typescript-axios -o ./client
6ไปทางกลับ: สร้าง Postman collection จาก OpenAPI
เอาไว้ทดสอบ API โดยไม่ต้องสร้าง request ทีละอัน
| วิธี | ทำอย่างไร |
|---|---|
| ในแอป Postman | กด Import แล้วเลือกไฟล์ OpenAPI (รองรับ OpenAPI 2.0, 3.0, 3.1) ได้ collection ที่มีโฟลเดอร์ request และตัวอย่าง response |
| CLI (เก็บไฟล์ในโปรเจกต์) | npm i -g openapi-to-postmanv2 แล้ว openapi2postmanv2 -s openapi.yml -o postman-collection.json -p |
| Postman API | ส่ง POST https://api.getpostman.com/import/openapi เพื่อนำเข้า OpenAPI เป็น collection ใหม่ (ต้องใช้ API key) |
🧱 โค้ดเต็มฝั่ง Backend (NestJS) ระบบ books
ไฟล์ครบทุกตัวของโปรเจกต์ library-api ที่สร้างด้วย nest g resource books วางทับได้เลย ข้อมูลเก็บในหน่วยความจำ (รีสตาร์ตแล้วหาย) field ใช้ title, author, price ถ้า DTO เดิมของคุณต่างจากนี้ให้ปรับให้ตรงกัน
โครงไฟล์
library-api/
└─ src/
├─ main.ts
├─ app.module.ts
└─ books/
├─ books.module.ts
├─ books.controller.ts
├─ books.service.ts
├─ dto/book.dto.ts
└─ entities/book.entity.tsไฟล์ใน src/books
app.module.ts และ main.ts
ใน main.ts เพิ่ม enableCors เพื่อให้ Next.js (พอร์ต 3001) เรียก API ได้ และเปิด whitelist ให้ตัด field แปลกปลอมทิ้ง
🖥️ ฝั่ง Frontend (Next.js) ต่อกับ books
ใช้ App Router + TypeScript หน้าแรกเป็น Server Component ดึงรายการหนังสือ ส่วนฟอร์มเพิ่ม/แก้ไขและปุ่มลบเป็น Client Component
1สร้างโปรเจกต์ Next.js
เปิด terminal ใหม่ ออกจากโฟลเดอร์ library-api แล้วสร้างโปรเจกต์ข้างๆ (ถ้ามีคำถามเพิ่ม เช่น React Compiler หรือ Turbopack ให้กด Enter ใช้ค่าเริ่มต้น)
cd .. npx create-next-app@latest library-web --typescript --app --no-tailwind --eslint --no-src-dir --import-alias "@/*" cd library-web
2ตั้งพอร์ตเป็น 3001
Nest ใช้พอร์ต 3000 อยู่แล้ว แก้ส่วน scripts ใน package.json
แล้วสร้างไฟล์ .env.local ที่รากโปรเจกต์ (ค่าที่ขึ้นต้น NEXT_PUBLIC_ ใช้ได้ทั้งฝั่ง server และ browser)
3โครงไฟล์
library-web/
├─ .env.local
├─ lib/api.ts
├─ components/
│ ├─ BookForm.tsx
│ └─ DeleteButton.tsx
└─ app/
├─ globals.css
├─ layout.tsx
├─ page.tsx (รายการ)
└─ books/
├─ new/page.tsx (เพิ่ม)
└─ [id]/edit/page.tsx (แก้ไข)สร้างโฟลเดอร์ lib และ components ที่รากโปรเจกต์เอง ส่วนไฟล์ใน app ให้วางทับไฟล์เดิม (ลบ app/page.module.css ทิ้งได้)
4ตัวเรียก API
5Components (Client)
6หน้าเว็บใน app/
7รันทั้งสองฝั่งและทดสอบ
เปิด 2 terminal แยกกัน
# terminal 1 (backend พอร์ต 3000) cd library-api npm run start:dev # terminal 2 (frontend พอร์ต 3001) cd library-web npm run dev
เปิด http://localhost:3001 ควรเห็นหนังสือ 2 เล่มจาก Nest ลองกดเพิ่ม แก้ไข และลบ แล้วเช็คที่ http://localhost:3000/docs ว่าข้อมูลตรงกัน
| อาการ | สาเหตุและวิธีแก้ |
|---|---|
| fetch failed / ECONNREFUSED | Backend ยังไม่รัน หรือ NEXT_PUBLIC_API_URL ผิด (แก้ .env.local แล้วต้องรีสตาร์ต npm run dev) |
| CORS error ใน browser | ลืม enableCors ใน main.ts หรือยังไม่รีสตาร์ต Nest |
| กดบันทึกแล้วได้ 400 | ข้อมูลไม่ผ่าน validation เช่น ชื่อว่าง หรือราคาติดลบ |
| Cannot find module '@/lib/api' | โฟลเดอร์ lib ต้องอยู่ที่รากโปรเจกต์ (ไม่ใช่ใน app) และ import alias ต้องเป็น @/* |
⚡ ฟังก์ชันของ Next.js ที่ใช้บ่อย
กดหัวข้อเพื่อดูฟังก์ชัน ตัวอย่าง และผลลัพธ์ อ้างอิงจาก App Router ถ้าใช้คนละเวอร์ชัน (บางส่วนเปลี่ยนใน Next 15 และ 16) ให้เทียบกับ nextjs.org/docs
📘 ฟังก์ชันจัดการตัวแปรแต่ละชนิดใน TypeScript
กดหัวข้อเพื่อเปิดดูตาราง ฟังก์ชัน ตัวอย่าง และผลลัพธ์ แต่ละแถวเป็นอิสระต่อกัน (ไม่ต่อเนื่องจากแถวก่อนหน้า) บางฟังก์ชัน เช่น replaceAll, at, Promise.any ต้องตั้ง target หรือ lib ใน tsconfig เป็น ES2022 ขึ้นไป (โปรเจกต์ NestJS รุ่นใหม่ตั้งไว้ให้แล้ว)
🇹🇭 เว็บภาษาไทยเรื่อง NestJS CRUD
พบ 9 ลิงก์ (บทความไทยมีน้อย) หลายชิ้นเก่า 2–5 ปี คำสั่งบางอย่างอาจเปลี่ยนไปแล้ว ให้เทียบกับ docs.nestjs.com