Day 2
PostgreSQL + Prisma — ให้ API ดึงข้อมูลจากฐานข้อมูลจริง
เวลา: 120 นาที
จบคาบนี้จะได้: http://localhost:3000/api/books คืนข้อมูลจากฐานข้อมูลจริง ไม่ใช่ array ในโค้ด และค้นหาได้
ช่วงที่ 1 — ติดตั้ง PostgreSQL (25 นาที)
🗺️ ตำแหน่งตอนนี้ในระบบ
- ✅ เตรียมเครื่องมือ (ทำไปแล้ว)
- ⚪ Angular (หน้าบ้าน) (ยังไม่ถึง)
- ✅ NestJS (หลังบ้าน) (ทำไปแล้ว)
- 🟢 PostgreSQL (ฐานข้อมูล) ← กำลังทำช่วงนี้
ต่อไป: ตั้งค่า Prisma เพื่อเชื่อมหลังบ้านเข้ากับฐานข้อมูล
1.1 ติดตั้ง PostgreSQL
ทำไมต้องมี: เป็น DBMS (ระบบจัดการฐานข้อมูล) ที่ทำงานเป็น server คือเป็นโปรแกรมที่รันค้างอยู่เบื้องหลังตลอดเวลา คอยรอรับคำสั่งจากโปรแกรมอื่น ต่างจากไฟล์ Excel ตรงที่หลายโปรแกรมเข้ามาอ่าน-เขียนพร้อมกันได้โดยข้อมูลไม่พัง
- เปิดเบราว์เซอร์ไปที่ enterprisedb.com/downloads/postgres-postgresql-downloads (เว็บทางการที่แจกตัวติดตั้ง Windows)
- เลือกดาวน์โหลด PostgreSQL 17 สำหรับ Windows x86-64
- ไฟล์ที่ได้จะชื่อประมาณ
postgresql-17.x-x-windows-x64.exe - ดับเบิลคลิกเพื่อเริ่มติดตั้ง (ถ้า Windows ถามสิทธิ์ admin ให้กด Yes)
ระหว่างติดตั้ง จะเจอหน้าจอเรียงกันแบบนี้:
| หน้าจอ | ต้องทำอะไร |
|---|---|
| Setup Wizard เปิดมา | กด Next |
| Installation Directory | ปล่อยค่า default (C:\Program Files\PostgreSQL\17) กด Next |
| Select Components | ปล่อยติ๊กไว้ทุกช่อง ทั้ง PostgreSQL Server, pgAdmin 4, Stack Builder, Command Line Tools — ใช้ครบทุกตัว กด Next |
| Data Directory | ปล่อยค่า default กด Next |
| Password | ใส่ postgres แล้วจำให้ได้ (ทั้งห้องใช้รหัสเดียวกันเพื่อไม่ให้สับสนตอนตั้งค่าคาบหน้า) พิมพ์ยืนยันอีกครั้งในช่องที่สอง |
| Port | ปล่อยเป็นค่า default 5432 |
| Advanced Options (Locale) | ปล่อยค่า default กด Next |
| Pre Installation Summary | ตรวจผ่าน ๆ แล้วกด Next |
| Ready to Install | กด Next เพื่อเริ่มติดตั้งจริง — ใช้เวลาไม่กี่นาที |
| จบการติดตั้ง | จะมีกล่องถามว่าจะเปิด Stack Builder ต่อไหม ให้เอาเครื่องหมายถูกออก แล้วกด Finish (Stack Builder ไว้โหลดเครื่องมือเสริมที่วิชานี้ไม่ได้ใช้ เปิดทิ้งไว้จะเสียเวลาเปล่า) |
💡 port 5432 = ช่องทางเข้าของ PostgreSQL คนละช่องกับหลังบ้านเรา (3000)
⚠️ ถ้าติดตั้งค้างหรือขึ้น error "database cluster installation failed" บางครั้งเกิดจากโปรแกรมป้องกันไวรัสของเครื่อง (เช่น Windows Defender) เข้าไปสแกนขณะกำลังสร้างไฟล์ฐานข้อมูล ลองปิดโปรแกรมป้องกันไวรัสชั่วคราวแล้วรันตัวติดตั้งใหม่อีกครั้ง หรือแจ้งอาจารย์เพื่อขอสิทธิ์ยกเว้นโฟลเดอร์
C:\Program Files\PostgreSQL
pgAdmin 4 จะติดตั้งมาให้พร้อมกันในขั้นตอนเดียวกัน (อยู่ในช่อง Select Components ที่ปล่อยติ๊กไว้) เป็นหน้าจอสำหรับดูและจัดการฐานข้อมูล ไม่ต้องไปหาโหลดแยก
1.2 สร้างฐานข้อมูล
เปิดโปรแกรม pgAdmin 4 จากเมนู Start (ครั้งแรกที่เปิดจะขอให้ตั้ง Master Password ของตัวโปรแกรมเอง — ตั้งอะไรก็ได้ จำไว้ใช้เฉพาะตอนเปิด pgAdmin ไม่เกี่ยวกับรหัสฐานข้อมูล)
จากนั้น: คลิกซ้ายที่ Servers → PostgreSQL 17 ครั้งแรกจะถามรหัสผ่าน ให้ใส่ postgres ที่ตั้งไว้ตอนติดตั้ง → คลิกขวาที่ Databases → Create → Database
ตั้งชื่อว่า booklab แล้วกด Save
💡 server / database / table ต่างกันอย่างไร
- server = ตัวอาคาร มีหนึ่งตัวบนเครื่องเรา
- database = ห้องในอาคาร แยกโปรเจกต์กัน โปรเจกต์นี้ใช้ห้อง
booklab- table = ตู้เอกสารในห้อง เก็บข้อมูลชนิดเดียวกัน เช่นตู้หนังสือ ตู้หมวดหมู่
✅ ต้องเห็น booklab โผล่ในรายการ Databases ทางซ้าย
ช่วงที่ 2 — ตั้งค่า Prisma (30 นาที)
🗺️ ตำแหน่งตอนนี้ในระบบ
- ✅ เตรียมเครื่องมือ (ทำไปแล้ว)
- ⚪ Angular (หน้าบ้าน) (ยังไม่ถึง)
- ✅ NestJS (หลังบ้าน) (ทำไปแล้ว)
- 🟢 PostgreSQL (ฐานข้อมูล) ← กำลังทำช่วงนี้
ต่อไป: ใส่ข้อมูลตั้งต้นลงฐานข้อมูล
2.1 ติดตั้งและเริ่มต้น Prisma
cd C:\webdev\booklab
npm install prisma@6 @prisma/client@6 --workspace api
cd apps\api
npm exec -- prisma init --datasource-provider postgresql
⚠️ ทำไมต้องระบุเลข
@6ทุกครั้ง Prisma มีเวอร์ชัน 7 ออกมาแล้วซึ่งเปลี่ยนวิธีตั้งค่าไปมาก (ย้ายไฟล์ตั้งค่าฐานข้อมูลไปที่อื่น ใช้งานยากขึ้น และหาตัวอย่างสอนบนอินเทอร์เน็ตยากกว่าเวอร์ชัน 6 มาก) ถ้าไม่ระบุเลขเวอร์ชันnpm execจะไปดึงเวอร์ชันล่าสุดมาให้เสมอ ซึ่งจะทำให้ขั้นตอนในใบงานนี้ใช้ไม่ได้เลย ต้องพิมพ์@6ต่อท้ายทุกครั้งที่ติดตั้ง Prisma ตลอดทั้งวิชานี้
💡 Prisma คืออะไร — และทำไมต้องมี ฐานข้อมูลพูดภาษา SQL ส่วนโค้ดเราพูดภาษา TypeScript Prisma คือล่ามที่แปลให้สองฝั่งคุยกันรู้เรื่อง
เครื่องมือประเภทนี้เรียกว่า ORM ประโยชน์คือเราเขียน
findMany()แทนที่จะต่อประโยค SQL เป็นข้อความเอง ซึ่งนอกจากอ่านง่ายกว่าแล้ว ยังกันคนแอบยัดคำสั่งอันตรายเข้ามาทางช่องกรอกข้อมูลได้ด้วย (จะอธิบายเรื่องนี้ละเอียดในคาบที่ 6)
✅ ต้องเกิดไฟล์ prisma/schema.prisma และ .env
✅ ตรวจสอบเวอร์ชันให้ชัวร์
npm exec -- prisma --version
ต้องเห็นเลข 6.x.x ถ้าเห็น 7.x.x แปลว่าขั้นตอนก่อนหน้าพลาด ให้กลับไปทำข้อ 2.1 ใหม่ตั้งแต่บรรทัดแรก
2.2 ใส่รหัสฐานข้อมูล
เปิดไฟล์ apps/api/.env แก้เป็น
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/booklab?schema=public"
💡 อ่าน connection string นี้ยังไง
postgresql://[ผู้ใช้]:[รหัสผ่าน]@[เครื่อง]:[port]/[ชื่อ database]💡 ทำไมต้องอยู่ในไฟล์
.envแยกต่างหาก เพราะมันมีรหัสผ่าน ถ้าเขียนปนไว้ในโค้ด แล้ววันหนึ่งเผลอส่งไฟล์นั้นให้คนอื่น (เช่น ส่งให้เพื่อนช่วยดู หรือแชร์โปรเจกต์ให้ใครดู) เท่ากับแจกรหัสฐานข้อมูลไปด้วย การแยกรหัสไว้ไฟล์เดียวทำให้จำง่ายว่า ไฟล์นี้ไฟล์เดียวที่ห้ามแชร์ให้ใครประโยชน์อีกข้อคือเวลาย้ายไปรันที่อื่น เช่นฐานข้อมูลบนคลาวด์ เราแก้แค่บรรทัดเดียว ไม่ต้องแก้โค้ดเลย
2.3 ออกแบบตาราง
ก่อนลงมือเขียนโค้ด มาวาด ER Diagram (Entity-Relationship Diagram) ให้เห็นภาพความสัมพันธ์ก่อน

💡 อ่านแผนผังนี้ยังไง
- แต่ละกล่องคือหนึ่งตาราง (Entity) ข้างในคือช่อง (column) ของตารางนั้น
- เส้นที่ลากเชื่อมสองกล่องคือ ความสัมพันธ์ (Relationship) เลข
1กับ∞(มาก) ที่ปลายเส้นบอกว่าฝั่งไหนมีได้กี่แถว — ฝั่ง Category มีได้ 1 แถวต่อการเชื่อมหนึ่งครั้ง ฝั่ง Book มีได้ หลายแถว (∞)PK= primary key,FK= foreign key,UNIQUE= ห้ามซ้ำ (ทบทวนความหมายได้จากการบ้านคาบที่แล้ว)วาด ER diagram แบบนี้ก่อนเขียนโค้ดเสมอ จะช่วยให้เห็นภาพรวมความสัมพันธ์ก่อนลงรายละเอียด และเป็นขั้นตอนมาตรฐานที่ใช้ในงานจริงเวลาออกแบบฐานข้อมูล
ลองสร้างตารางแรกด้วยมือผ่าน pgAdmin ก่อน
ก่อนจะให้ Prisma ทำงานแทนทั้งหมด ลองสร้างตาราง Category (ตารางแรกจากแผนผังข้างบน) ด้วยมือผ่านหน้าจอ pgAdmin ดูสักครั้ง จะได้เห็นภาพว่าเบื้องหลัง Prisma ไปทำอะไรให้เราบ้าง
ใน pgAdmin คลิกขวาที่ Schemas → public → Tables เลือก Create → Table
| ช่อง | ใส่ค่า |
|---|---|
| แท็บ General → Name | Category |
| แท็บ Columns → กด + เพิ่มช่องที่ 1 | Name: id, Data type: integer, ติ๊ก Primary Key ติ๊ก Identity (ให้ฐานข้อมูลนับเลขให้เอง) |
| แท็บ Columns → กด + เพิ่มช่องที่ 2 | Name: name, Data type: character varying, ติ๊ก Not Null, ติ๊ก Unique |
กด Save (ไอคอนรูปแผ่นดิสก์) แล้ว refresh ที่ Tables จะเห็นตาราง Category เกิดขึ้นจริง
💡 สังเกตว่าขั้นตอนนี้ทำเองทีละคลิกล้วนๆ ไม่มีที่เก็บว่าใครสร้างตารางนี้เมื่อไหร่ ด้วยเหตุผลอะไร และถ้าจะสร้างตาราง
Bookต่อ (ที่มี foreign key โยงกลับมาที่Categoryแบบในแผนผัง) ก็ต้องตั้งค่าความสัมพันธ์เองอีกหลายคลิก แถมเพื่อนที่ clone โปรเจกต์นี้ไปต้องมานั่งคลิกสร้างซ้ำเป๊ะๆ ทุกขั้นตอนเอง
ลบตารางทดลองนี้ทิ้งก่อนไปต่อ — คลิกขวาที่ Category → Delete/Drop เพราะขั้นตอนถัดไปเราจะให้ Prisma สร้างให้เองทั้งหมด (รวม Book ด้วย) จากไฟล์เดียว พร้อมเก็บประวัติการเปลี่ยนแปลงให้อัตโนมัติ — นี่คือเหตุผลที่โลกจริงใช้ ORM/migration tool แทนการคลิกสร้างตารางเองแบบที่เพิ่งทำ
จากแผนผังนี้ แปลงเป็นไฟล์ schema.prisma ได้ตรงๆ — แทนที่ทั้งไฟล์ apps/api/prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Category {
id Int @id @default(autoincrement())
name String @unique
books Book[]
}
model Book {
id Int @id @default(autoincrement())
title String
author String
year Int
categoryId Int
category Category @relation(fields: [categoryId], references: [id])
}
💡 ไฟล์นี้คือแปลนบ้าน กระดาษแผ่นเดียวที่วาดไว้ว่าฐานข้อมูลมีตารางอะไร แต่ละตารางมีช่องอะไร และเชื่อมกันตรงไหน
💡 อ่านทีละบรรทัด
model Category= ตารางชื่อ Category@id= ช่องนี้คือ primary key ตัวระบุแถวที่ไม่ซ้ำกัน (ที่ทบทวนมาจากวิชาฐานข้อมูล)@default(autoincrement())= ให้ฐานข้อมูลแจกเลขให้เองทีละ 1@unique= ห้ามซ้ำ จะมีหมวดหมู่ชื่อเดียวกันสองอันไม่ได้categoryId Int= foreign key ช่องที่เก็บว่าหนังสือเล่มนี้อยู่หมวดไหน@relation(...)= บอก Prisma ว่าcategoryIdชี้ไปที่idของตาราง Categorybooks Book[]ในฝั่ง Category = หมวดหนึ่งมีหนังสือได้หลายเล่มทั้งหมดนี้คือความสัมพันธ์แบบ one-to-many — หนึ่งหมวดหมู่ ต่อ หลายเล่ม
2.4 สร้างตารางจริงในฐานข้อมูล
npm exec -- prisma migrate dev --name init
💡 migration คืออะไร คือบันทึกการต่อเติม ครั้งแรกสร้างตาราง ครั้งต่อไปเพิ่มช่อง เก็บไว้เป็นลำดับ เหมือน commit ของฐานข้อมูล
ประโยชน์คือเพื่อนที่ clone โปรเจกต์ไป รันคำสั่งเดียวก็ได้โครงสร้างตารางเหมือนเราเป๊ะ ไม่ต้องมานั่งสร้างตารางเองทีละช่อง
✅ ตรวจสอบ: กลับไปที่ pgAdmin → กด refresh ที่ booklab → เข้า Schemas → public → Tables ต้องเห็นตาราง Book และ Category
ลองคลิกขวาที่ตาราง Book → Properties → Columns ดูว่าช่องที่เราเขียนใน schema กลายเป็นคอลัมน์จริงครบไหม
ช่วงที่ 3 — ใส่ข้อมูลตั้งต้น (25 นาที)
🗺️ ตำแหน่งตอนนี้ในระบบ
- ✅ เตรียมเครื่องมือ (ทำไปแล้ว)
- ⚪ Angular (หน้าบ้าน) (ยังไม่ถึง)
- ✅ NestJS (หลังบ้าน) (ทำไปแล้ว)
- 🟢 PostgreSQL (ฐานข้อมูล) ← กำลังทำช่วงนี้
ต่อไป: แก้ API ให้ดึงข้อมูลจากฐานข้อมูลจริงแทน array
3.1 เขียนสคริปต์ seed
สร้างไฟล์ apps/api/prisma/seed.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
await prisma.book.deleteMany();
await prisma.category.deleteMany();
const programming = await prisma.category.create({
data: { name: 'Programming' },
});
const design = await prisma.category.create({
data: { name: 'Design' },
});
await prisma.book.createMany({
data: [
{ title: 'Clean Code', author: 'Robert C. Martin', year: 2008, categoryId: programming.id },
{ title: 'The Pragmatic Programmer', author: 'Andy Hunt', year: 1999, categoryId: programming.id },
{ title: 'Refactoring', author: 'Martin Fowler', year: 1999, categoryId: programming.id },
{ title: 'The Design of Everyday Things', author: 'Don Norman', year: 1988, categoryId: design.id },
{ title: "Don't Make Me Think", author: 'Steve Krug', year: 2000, categoryId: design.id },
],
});
console.log('เพิ่มข้อมูลตั้งต้นเรียบร้อย');
}
main().finally(() => prisma.$disconnect());
💡 seed = ของตั้งต้นที่ใส่ไว้ให้ เหมือนบ้านตัวอย่างที่มีเฟอร์นิเจอร์วางแล้ว จะได้ไม่ต้องเปิดมาเจอห้องว่าง
💡
PrismaClient= ตัวล่ามที่เราเรียกใช้จริงในโค้ด 💡deleteMany()สองบรรทัดแรก = ล้างของเก่าก่อน เพื่อให้รันซ้ำกี่ครั้งก็ได้ผลเหมือนเดิม 💡 ต้องสร้าง category ก่อน book เพราะหนังสือต้องมีcategoryIdที่มีอยู่จริง ไม่งั้นฐานข้อมูลจะปฏิเสธ — นี่คือ foreign key ทำงาน
3.2 บอก Prisma ว่าไฟล์ seed อยู่ตรงไหน
เปิดไฟล์ apps/api/package.json เพิ่มช่องนี้ (วางไว้ระดับเดียวกับ "scripts" หรือ "dependencies" ก็ได้ ไม่ต้องอยู่ในนั้น)
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
⚠️ ห้ามข้ามขั้นตอนนี้ ถ้าไม่ใส่
npm exec -- prisma db seedในขั้นถัดไปจะรันจบแบบไม่มี error และไม่มี output ใด ๆ เลย (เงียบสนิท) ทำให้เข้าใจผิดว่าใส่ข้อมูลสำเร็จทั้งที่ตารางยังว่างอยู่ — สาเหตุคือ Prisma ไม่รู้ว่าไฟล์ seed อยู่ที่ไหนถ้าไม่ได้ระบุไว้ตรงนี้💡
ts-node= ตัวรัน TypeScript ตรง ๆ โดยไม่ต้อง compile เป็น JavaScript ก่อน ติดมาให้แล้วตอนสร้างโปรเจกต์ NestJS ในคาบ 1 ไม่ต้องติดตั้งเพิ่ม
3.3 รัน seed
npm exec -- prisma db seed
✅ ต้องเห็น เพิ่มข้อมูลตั้งต้นเรียบร้อย (อาจมี warning สีเหลืองเรื่อง package.json#prisma deprecated ปนมาด้วย — ไม่เป็นไร ข้ามได้ ยังใช้งานได้ปกติ)
3.4 ดูข้อมูลด้วย Prisma Studio
เปิด Terminal ใหม่
cd C:\webdev\booklab\apps\api
npm exec -- prisma studio
เบราว์เซอร์จะเปิดที่ localhost:5555

💡 Prisma Studio = หน้าจอดูข้อมูลแบบตาราง คล้าย Excel แก้ค่าได้ทันที และกดที่ช่อง category แล้วเด้งไปดูหมวดหมู่ที่เชื่อมกันได้ ซึ่งทำให้เห็นภาพ relation ชัดกว่าดูใน pgAdmin
ตั้งแต่คาบนี้ไป เวลาสงสัยว่า "ทำไมหน้าเว็บว่าง" ให้เปิดตัวนี้ดูก่อนเสมอ จะรู้ใน 3 วินาทีว่าไม่มีข้อมูล หรือโค้ดผิด
⚠️ Terminal นี้จะถูกยึดไว้ ถ้าจะพิมพ์คำสั่งอื่นให้เปิดอันใหม่
3.5 ลองเขียน SQL เอง
กลับไปที่ pgAdmin → คลิกขวาที่ booklab → Query Tool แล้วลองสองคำสั่งนี้
SELECT * FROM "Book";
SELECT b.title, b.year, c.name AS category
FROM "Book" b
JOIN "Category" c ON b."categoryId" = c.id
ORDER BY b.year;
💡 นี่คือสิ่งที่ Prisma แปลให้เราตลอดเวลา คำสั่งที่สองคือ JOIN ที่เรียนมาในวิชาฐานข้อมูล — ดึงข้อมูลจากสองตารางมาต่อกันโดยใช้ foreign key เป็นตัวเชื่อม
ต่อไปเราจะไม่เขียน SQL เองแล้ว แต่ต้องรู้ว่าเบื้องหลังมันคือสิ่งนี้
⚠️ ต้องใส่เครื่องหมาย
"ครอบชื่อตาราง เพราะ Prisma ตั้งชื่อตารางด้วยตัวพิมพ์ใหญ่ ถ้าไม่ใส่ PostgreSQL จะมองเป็นตัวพิมพ์เล็กแล้วหาไม่เจอ
ช่วงที่ 4 — ต่อ API เข้ากับฐานข้อมูล (35 นาที)
🗺️ ตำแหน่งตอนนี้ในระบบ
- ✅ เตรียมเครื่องมือ (ทำไปแล้ว)
- ⚪ Angular (หน้าบ้าน) (ยังไม่ถึง)
- 🟢 NestJS (หลังบ้าน) ← กำลังทำช่วงนี้
- 🟢 PostgreSQL (ฐานข้อมูล) ← กำลังทำช่วงนี้
ต่อไป: คาบหน้า: เริ่มสร้างหน้าบ้านด้วย Angular
4.1 สร้างตัวเชื่อม Prisma เข้ากับ NestJS
cd apps\api
npm exec -- nest g service prisma --no-spec --flat --skip-import
💡 ทำไมต้องใส่ flag เพิ่ม
--flat= สร้างไฟล์ไว้ที่src/prisma.service.tsตรง ๆ ไม่สร้างโฟลเดอร์prisma/มาครอบ (ต่างจากbooksที่อยากได้โฟลเดอร์แยก)--skip-import= ตัวนี้ไม่ผูกกับแผนกไหนแผนกหนึ่งโดยเฉพาะ จะเอาไปใช้ในแผนกหนังสือเอง ไม่ต้องให้ CLI เอาไปลงทะเบียนในapp.module.tsให้อัตโนมัติ
✅ ต้องเห็น ไฟล์ apps/api/src/prisma.service.ts เกิดขึ้น เปิดดูจะเป็นโครงเปล่า ๆ แบบนี้
import { Injectable } from '@nestjs/common';
@Injectable()
export class PrismaService {}
แก้ไฟล์นี้ให้เป็นแบบนี้
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}
💡
@Injectable()= ป้ายบอก NestJS ว่า "คลาสนี้เป็นเครื่องมือที่แจกให้ที่อื่นใช้ได้" 💡onModuleInit= ฟังก์ชันที่ NestJS เรียกให้อัตโนมัติตอนโปรแกรมเริ่มทำงาน เราใช้จังหวะนี้เชื่อมต่อฐานข้อมูล
4.2 สร้าง Service ของหนังสือ
npm exec -- nest g service books --no-spec
💡 service ต่างจาก controller อย่างไร
- controller = พนักงานต้อนรับ รับเรื่องแล้วส่งต่อ ไม่ทำงานหนักเอง
- service = คนทำงานหลังบ้านจริง ๆ คนที่เดินไปหยิบของจากฐานข้อมูล
แยกกันเพราะถ้าวันหนึ่งอยากเปลี่ยนวิธีเก็บข้อมูล เราแก้แค่ service ไม่ต้องแตะ controller เลย
แทนที่ทั้งไฟล์ apps/api/src/books/books.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma.service';
@Injectable()
export class BooksService {
constructor(private prisma: PrismaService) {}
findAll(keyword?: string) {
return this.prisma.book.findMany({
where: keyword
? { title: { contains: keyword, mode: 'insensitive' } }
: undefined,
include: { category: true },
orderBy: { id: 'asc' },
});
}
findOne(id: number) {
return this.prisma.book.findUnique({
where: { id },
include: { category: true },
});
}
}
💡
constructor(private prisma: PrismaService)บรรทัดนี้แปลว่า "ฉันต้องใช้ PrismaService" แล้ว NestJS จะหยิบมาส่งให้เอง เราไม่ต้องสร้างเอง💡 อ่านคำสั่ง Prisma
findMany()= ขอหลายแถว (เทียบเท่าSELECT * FROM)where= เงื่อนไข (เทียบเท่าWHERE)contains= มีคำนี้อยู่ข้างใน (เทียบเท่าLIKE '%คำ%')mode: 'insensitive'= ไม่สนตัวพิมพ์เล็กใหญ่include: { category: true }= ดึงข้อมูลหมวดหมู่ที่เชื่อมกันมาด้วย (เทียบเท่าJOINที่เพิ่งลองใน pgAdmin)findUnique()= ขอแถวเดียวด้วย primary key
4.3 ให้ module รู้จัก PrismaService
แทนที่ apps/api/src/books/books.module.ts
import { Module } from '@nestjs/common';
import { BooksController } from './books.controller';
import { BooksService } from './books.service';
import { PrismaService } from '../prisma.service';
@Module({
controllers: [BooksController],
providers: [BooksService, PrismaService],
})
export class BooksModule {}
💡
providers= รายชื่อเครื่องมือที่แผนกนี้ใช้ได้ ถ้าไม่ใส่ NestJS จะบอกว่าหาไม่เจอ
4.4 แก้ controller ให้เรียก service
แทนที่ apps/api/src/books/books.controller.ts
import { Controller, Get, Param, Query, ParseIntPipe } from '@nestjs/common';
import { BooksService } from './books.service';
@Controller('books')
export class BooksController {
constructor(private booksService: BooksService) {}
@Get()
findAll(@Query('q') q?: string) {
return this.booksService.findAll(q);
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.booksService.findOne(id);
}
}
💡
@Query('q')= รับค่าจาก URL ที่ต่อท้ายด้วย?q=...💡@Param('id')= รับค่าจากส่วนของ URL เช่น/api/books/3จะได้id = 3💡ParseIntPipe= ตัวแปลงค่าจากข้อความเป็นตัวเลข เพราะทุกอย่างที่มาจาก URL เป็นข้อความหมด ถ้าแปลงไม่ได้จะตีกลับเป็น error ให้อัตโนมัติ⚠️
@Get(':id')ต้องอยู่ล่างสุดเสมอ เพราะมันรับได้ทุกอย่าง ถ้าเอาไว้บน endpoint อื่นจะถูกมันกินหมด
4.5 ทดสอบ
ดู Terminal ที่รัน npm run dev:api ว่าโหลดใหม่แล้ว จากนั้นลองทั้งสาม URL
| URL | ควรได้ |
|---|---|
localhost:3000/api/books |
หนังสือ 5 เล่ม พร้อมข้อมูล category |
localhost:3000/api/books?q=clean |
เฉพาะ Clean Code |
localhost:3000/api/books/1 |
หนังสือเล่มเดียว |
✅ สังเกตว่าแต่ละเล่มมี "category": { "id": 1, "name": "Programming" } ติดมาด้วย — นั่นคือผลของ include ที่เราเขียนไว้
🚀 ถ้าเสร็จก่อนเพื่อน
- เพิ่ม endpoint
/api/categoriesคืนรายชื่อหมวดหมู่พร้อมจำนวนหนังสือในแต่ละหมวด (คำใบ้:_count) - เพิ่มหนังสือใหม่ 3 เล่มใน
seed.tsแล้วรันnpm exec -- prisma db seedใหม่ - ลองค้นด้วยชื่อผู้แต่งแทนชื่อหนังสือ
❓ ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
P1000 Authentication failed |
รหัสใน .env ผิด |
ตรวจว่าเป็น postgres:postgres |
| pgAdmin ขอรหัสไม่เหมือนที่ตั้งไว้ตอนติดตั้ง | สับสนระหว่าง Master Password (ของ pgAdmin เอง) กับรหัสฐานข้อมูล | รหัสฐานข้อมูลคือ postgres ใช้ตอนคลิก Server ครั้งแรกเท่านั้น |
P1001 Can't reach database server |
PostgreSQL ไม่ได้รันอยู่ | เปิด Services ของ Windows หา postgresql แล้วสั่ง Start |
P2003 Foreign key constraint failed |
ใส่ categoryId ที่ไม่มีอยู่จริง |
ตรวจว่าสร้าง category ก่อน book |
Cannot find module '@prisma/client' |
ยังไม่ได้ generate | รัน npm exec -- prisma generate |
| แก้ schema แล้วโค้ดไม่รู้จักช่องใหม่ | ยังไม่ได้ migrate | รัน npm exec -- prisma migrate dev --name ... |
| ข้อมูลเละอยากเริ่มใหม่ | — | npm exec -- prisma migrate reset แล้ว seed ใหม่ |
📌 คำสั่ง Prisma ที่ต้องจำ
| คำสั่ง | ใช้เมื่อ |
|---|---|
npm exec -- prisma migrate dev --name ชื่อ |
แก้ schema.prisma แล้วต้องการให้ตารางจริงเปลี่ยนตาม |
npm exec -- prisma db seed |
ใส่ข้อมูลตั้งต้นใหม่ |
npm exec -- prisma studio |
เปิดดูข้อมูล |
npm exec -- prisma migrate reset |
ล้างทุกอย่างกลับเป็นค่าตั้งต้น |
คาบหน้า เริ่มทำหน้าบ้านด้วย Angular