
กำลังโหลด...

กำลังโหลด...

งานสุดท้ายคือเปิด Books Web ที่เตรียมไว้ แล้วทำให้ Register, Login, List, Add และ Delete เดินถึง API จริง ถ้าเห็น Request ใน Browser และ row ใน PostgreSQL เปลี่ยน แปลว่าคุณตามข้อมูลได้ครบทั้งระบบ
Chapter นี้ไม่ได้ให้สร้าง React จากศูนย์ เราใช้ folder books-web ที่เตรียมไว้ แล้วตาม Request จริงจากปุ่มบนหน้าจอไปจนถึง PostgreSQL
flowchart LR
N["Next.js + Tailwind\nhttp://localhost:3001"] -->|"fetch + JSON"| E["Express books-api\nhttp://localhost:3000"]
E -->|"SQL"| P["PostgreSQL\nbooks_db"]
E -->|"JWT token"| N
| Server | Port | หน้าที่ |
|---|---|---|
Express books-api |
3000 |
API, JWT, SQL, PostgreSQL |
Next.js books-web |
3001 |
หน้าจอ Tailwind ที่คนใช้กด |
เปิด Terminal สองหน้าต่าง:
# Terminal 1 — ใน books-api
npm run dev
# Terminal 2 — ใน books-web
npm install
npm run dev
Browser มอง http://localhost:3001 และ http://localhost:3000 เป็นคนละ origin เพราะ port ต่างกัน แม้อยู่เครื่องเดียวกัน
Postman เรียก API ได้โดยไม่ติด CORS แต่ Browser จะบล็อกจนกว่า Express จะประกาศว่า “อนุญาตให้ Next.js เครื่องนี้เรียกได้”
ใน folder books-api ติดตั้ง:
npm install cors
ใน index.js เพิ่ม import ด้านบน:
import cors from "cors";
แล้ววาง middleware นี้ ก่อน app.use(express.json()) และก่อน routes:
app.use(cors({
origin: "http://localhost:3001",
}));
app.use(express.json());
sequenceDiagram
participant B as Browser :3001
participant E as Express :3000
B->>E: POST /auth/login
E-->>B: Access-Control-Allow-Origin: :3001
B->>E: request จริง
E-->>B: JSON response
| ตรวจอะไร | คำตอบที่ควรเป็น |
|---|---|
| Next รัน port ไหน | 3001 |
origin ใน Express |
http://localhost:3001 ต้องตรงเป๊ะ |
ตำแหน่ง app.use(cors(...)) |
ก่อน routes |
| หลังแก้ Express | restart npm run dev |
เราใช้ Bearer token ไม่ใช้ cookie จึงยังไม่ต้องตั้ง
credentials: trueในคาบนี้
folder reference อยู่ที่:
fullstack-starter/books-web
ดาวน์โหลด Books Web Starter (.zip)
โครงสร้างสำคัญ:
books-web/
├─ app/register/page.js # ฟอร์ม Register
├─ app/login/page.js # Login และเก็บ token
├─ app/books/page.js # รายการหนังสือ
├─ app/books/[id]/page.js # รายละเอียด + Delete
├─ app/books/new/page.js # เพิ่มหนังสือ
└─ components/Nav.js # เมนูร่วม
Copy .env.local.example เป็น .env.local แล้วตั้ง:
# TODO: ต้องตรงกับ URL ของ Express books-api
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000
คำว่า NEXT_PUBLIC_ จำเป็น เพราะทุกหน้าใช้ fetch() ใน Browser และ URL นี้ไม่ใช่ secret
fetch() ในหน้าที่ใช้งานจริงไม่ต้องมี helper folder เพิ่ม เด็กจะเห็น Request อยู่ในหน้าที่กดปุ่มนั้นเลย เช่น app/books/new/page.js:
const response = await fetch(`${API_BASE_URL}/books`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ title, author, year }),
});
| หน้าเว็บ | fetch() ไป Endpoint Express |
|---|---|
app/register/page.js |
POST /auth/register |
app/login/page.js |
POST /auth/login และคืน { token } |
app/books/page.js |
GET /books |
app/books/[id]/page.js |
GET /books/:id และ DELETE /books/:id + Bearer token |
app/books/new/page.js |
POST /books + Bearer token |
ในหน้า app/books/new/page.js Frontend อ่าน token จาก browser แล้วส่ง Bearer token:
headers: {
Authorization: `Bearer ${token}`,
}
ดังนั้นใน books-api/index.js ให้แทนที่ POST /books เดิมที่เขียนลง JSON file ด้วย route นี้:
app.post("/books", verifyToken, async (req, res) => {
const { title, author, year } = req.body;
if (!title || !author) {
return res.status(400).json({ error: "ต้องระบุ title และ author" });
}
try {
const result = await pool.query(
`INSERT INTO books (title, author, year, user_id)
VALUES ($1, $2, $3, $4)
RETURNING *`,
[title, author, year ?? null, req.user.userId]
);
res.status(201).json(result.rows[0]);
} catch (error) {
console.error(error);
res.status(500).json({ error: "Database query failed" });
}
});
จุดสำคัญ: user_id ต้องมาจาก req.user.userId ไม่ใช่ form บนหน้าเว็บ เพราะผู้ใช้แก้ค่าจาก Browser ได้
/register สร้าง user/login แล้วดูว่า Next เก็บ token เพื่อส่งกับ Request ถัดไป/books ดูรายการจาก PostgreSQL/books/new เพิ่มหนังสือSELECT * FROM books; เพื่อเห็น row ใหม่และ user_idAuthorization: Bearer ...ตัวอย่างนี้ใช้ localStorage เพื่อให้เห็น token ง่ายในคาบเท่านั้น Production ควรออกแบบการเก็บ token และ session ให้รัดกุมกว่านี้

ทำตาม 5 จุดในหน้า Books Web:
/books ให้เห็นรายการจาก Express/books/newถ้า Browser ขึ้น CORS error ให้ตรวจ app.use(cors({ origin: "http://localhost:3001" })) ที่ Express ก่อน
ข้อ 1. ค่าจากผู้ใช้ควรส่งเข้า SQL แบบใด
ข้อ 2. Database ควรเก็บ password แบบใด
ข้อ 3. ส่วนใดตรวจว่าเป็นเจ้าของหนังสือ