จากเว็บไซต์ธรรมดา สู่ PERN และ Nextbase
HTML/CSS/JavaScript → React → PERN → Nextbase คือ ลำดับการเรียนรู้และการแก้ปัญหา ไม่ใช่ลำดับที่ของใหม่มาแทนของเก่าทั้งหมด หน้านี้อธิบายว่าแต่ละเครื่องมืออยู่ตรงไหน เกิดขึ้นมาเพื่อแก้อะไร และเลือกใช้อย่างไร พร้อมโค้ดที่รันได้จริง
At a glance
| หัวข้อ | ตอบคำถาม |
|---|---|
| Client, Server, Database | 3 ส่วนของเว็บต่างกันอย่างไร |
| HTTP และ API | request เดินทางอย่างไร และ business logic อยู่ตรงไหน |
| ทำไมต้องมี React | แก้ DOM เองต่างจากเขียน component อย่างไร |
| PERN Stack | PostgreSQL, Express, React และ Node.js อยู่ตรงไหนของระบบ |
| Nextbase แบบ A–D | Next.js + Supabase ดึงและเขียนข้อมูลได้กี่ทาง |
| เลือก architecture | งานแบบไหนใช้ทางไหน รวมถึง portfolio |
| ประวัติศาสตร์ | ทำไมเครื่องมือเหล่านี้ถึงเกิดขึ้น |
| Git ฉบับเข้าใจหลักการ | จากไฟล์ final_v2 สู่ commit, branch และ pull request |
Client, Server, Database
Client คือฝั่งผู้ใช้ เช่นเบราว์เซอร์บนคอมหรือมือถือ Server คือระบบที่รับ request แล้วให้บริการ Database คือที่เก็บข้อมูลถาวร จะอยู่เครื่องเดียวกับ server หรือคนละเครื่องก็ได้
- ClientBrowser: แสดง UI รับการกด เรียก API
- Serverตรวจสิทธิ์ ประมวลผล business logic
- Databaseตาราง ความสัมพันธ์ ธุรกรรม
| ส่วน | ทำอะไร | ตัวอย่าง |
|---|---|---|
| Client side | แสดง UI, รับการกดปุ่ม, อ่าน input, เรียก HTTP API | HTML/CSS/JS ที่เบราว์เซอร์โหลดมา, React component |
| Server side | ตรวจสิทธิ์, คำนวณตามกฎของแอป, ติดต่อระบบอื่น, อาจสร้าง HTML ก่อนส่ง | Express API, Next.js บน Vercel |
| Database | เก็บข้อมูลที่ต้องอยู่ถาวร เช่น นักเรียน วิชา คะแนน | PostgreSQL บน Supabase |
ไม่จำเป็นต้องมีครบทุกส่วน portfolio แบบ static ไม่มี API และฐานข้อมูลของตัวเองเลยก็ได้ (ดู Portfolio ต้องใช้อะไร)
HTTP, API และ business logic
| คำ | ความหมาย |
|---|---|
| HTTP | protocol สำหรับ request และ response |
| API | ช่องทางที่ระบบเปิดให้คนอื่นเรียกใช้ความสามารถ |
| API endpoint | URL + HTTP method ที่รับ request เช่น GET /api/scores |
| Business logic | กฎของแอป เช่น ใครแก้คะแนนได้ คะแนนรวมคิดอย่างไร |
- Browser
fetch("/api/scores") - API endpoint
GET /api/scores - Business logicนักเรียนเห็นแค่คะแนนตัวเอง
- Databasequery คะแนน
- JSON responseclient แสดงผล
ลองรันจริง
สร้างโฟลเดอร์ใหม่ รัน npm init -y และ npm install express แล้วสร้าง 2 ไฟล์นี้
// server.js
const express = require("express");
const app = express();
// ข้อมูลตัวอย่างแทนฐานข้อมูล
const scores = [
{ student: "mint", subject: "Math", score: 18 },
{ student: "mint", subject: "Science", score: 15 },
{ student: "beam", subject: "Math", score: 12 },
];
// Business logic: ครูเห็นทุกคน นักเรียนเห็นแค่ของตัวเอง
function visibleScores(user) {
if (user.role === "teacher") return scores;
return scores.filter((s) => s.student === user.name);
}
// API endpoint
app.get("/api/scores", (req, res) => {
// ระบบจริงจะอ่าน user จากการล็อกอิน ตัวอย่างนี้ส่งผ่าน query เพื่อให้ลองง่าย
const user = { name: req.query.user, role: req.query.role ?? "student" };
res.json(visibleScores(user));
});
app.use(express.static("public")); // เสิร์ฟหน้าเว็บในโฟลเดอร์ public
app.listen(3000, () => console.log("http://localhost:3000"));<!-- public/index.html -->
<h1>คะแนนของฉัน</h1>
<ul id="list"></ul>
<script>
async function load() {
const response = await fetch("/api/scores?user=mint");
const scores = await response.json();
document.querySelector("#list").innerHTML = scores
.map((s) => `<li>${s.subject}: ${s.score}</li>`)
.join("");
}
load();
</script>รัน node server.js แล้วเปิด http://localhost:3000 จะเห็นแค่คะแนนของ mint ลองเปิด http://localhost:3000/api/scores?role=teacher ตรง ๆ จะได้ JSON ของทุกคน
ศัพท์ที่มักปนกันBusiness logic = กฎของระบบ · Backend logic = งานทั้งหมดฝั่ง backend รวม business logic, validation, auth · Server logic = โค้ดใด ๆ ที่รันบน server รวมการ render ด้วย สามคำนี้ไม่ได้ใช้แทนกันได้ตรงตัว
ทำไมต้องมี React
HTML บอกโครงสร้าง CSS บอกหน้าตา JavaScript เพิ่มพฤติกรรมและแก้ DOM ได้ การอัปเดตบางส่วนของหน้าโดยไม่ reload เกิดจาก JavaScript แก้ DOM และดึงข้อมูลแบบ asynchronous ไม่ได้เกี่ยวกับว่า HTML หรือ CSS ช้า
พอ UI ใหญ่ขึ้น เช่น feed, comment, like, notification การสั่งแก้ DOM ทีละจุดเริ่มดูแลยาก React ให้เราบอกว่า UI ควรหน้าตาเป็นอย่างไรตาม state แล้ว React อัปเดต DOM ให้
ปุ่มนับเลข 2 แบบ
<!-- index.html: เปิดในเบราว์เซอร์ได้เลย -->
<button id="like">❤️ <span id="count">0</span></button>
<script>
let count = 0;
document.querySelector("#like").addEventListener("click", () => {
count += 1;
// ต้องสั่งเองว่า element ไหนเปลี่ยน
document.querySelector("#count").textContent = count;
});
</script>แบบ imperative: เราบอกทีละขั้นว่าต้องแก้อะไร ถ้ามีตัวเลขเดียวกันโชว์ 3 ที่ ต้องจำแก้ให้ครบ 3 ที่
// components/LikeButton.tsx (ใช้ใน Next.js starter ของ Day 2 ได้เลย)
"use client";
import { useState } from "react";
export default function LikeButton() {
const [likes, setLikes] = useState(0);
return <button onClick={() => setLikes(likes + 1)}>❤️ {likes}</button>;
}แบบ declarative: เราบอกว่า UI หน้าตาเป็นอย่างไรเมื่อ likes มีค่าเท่านี้ พอ state เปลี่ยน React แก้ DOM ให้เอง
จาก HTML เมื่อวาน สู่ component วันนี้
// เมื่อวาน (HTML)
// <div class="card"><h2>My Project</h2><p>My first website</p></div>
// วันนี้ (JSX ใน React): 1 component ใช้กับข้อมูลหลายชุด
type Project = { title: string; description: string };
function ProjectCard({ title, description }: Project) {
return (
<article className="card">
<h2>{title}</h2>
<p>{description}</p>
</article>
);
}
const projects: Project[] = [
{ title: "Portfolio", description: "My first website" },
{ title: "Calculator", description: "A small JS app" },
];
export default function Home() {
return (
<main>
<h1>My Projects</h1>
{projects.map((p) => (
<ProjectCard key={p.title} {...p} />
))}
</main>
);
}classเปลี่ยนเป็นclassNameเพราะ JSX เป็น JavaScript และclassเป็นคำสงวน{title}คือการแทรก JavaScript expression ลงใน UI- ชื่อ component ขึ้นต้นด้วยตัวพิมพ์ใหญ่ เช่น
ProjectCard .map()สร้าง card จาก array ไม่ต้อง copy HTML ซ้ำ 5 รอบ
JSX เป็น syntax สำหรับเขียน React UI ไม่ใช่ HTML รุ่นใหม่ และไม่ได้เกิดก่อน React React ใช้ได้โดยไม่มี Next.js แต่ Next.js ใช้ React เป็นระบบ UI
PERN Stack
- ReactClient: UI และ state
- Express บน Node.jsServer: route, middleware, business logic
- PostgreSQLDatabase: SQL, relation, transaction
| ตัว | หน้าที่ | อยู่ที่ |
|---|---|---|
| PostgreSQL | SQL, ความสัมพันธ์ระหว่างตาราง, transaction | Database |
| Express | กำหนด API endpoint และ middleware | Server (บน Node.js) |
| React | สร้าง UI จาก component และ state | Client |
| Node.js | runtime ที่ทำให้ JavaScript รันนอกเบราว์เซอร์ | Server |
| Business logic | กฎของแอป เช่น การลงทะเบียนเรียน การคิดคะแนน | ฟังก์ชันฝั่ง server ที่ Express เรียก |
โค้ดครบเส้นทาง
-- 1. Database: สร้างตาราง (รันใน psql หรือ SQL Editor)
create table students (
id serial primary key,
name text not null,
grade int not null
);
insert into students (name, grade) values ('Mint', 11), ('Beam', 12);// 2. Server: backend/server.js (npm install express pg cors dotenv)
require("dotenv").config();
const express = require("express");
const cors = require("cors");
const { Pool } = require("pg");
const app = express();
const db = new Pool({ connectionString: process.env.DATABASE_URL });
app.use(cors());
app.use(express.json());
app.get("/api/students", async (req, res) => {
const { rows } = await db.query("select id, name, grade from students order by id");
res.json(rows);
});
app.post("/api/students", async (req, res) => {
const { name, grade } = req.body;
// Validation คือส่วนหนึ่งของ backend logic: อย่าเชื่อข้อมูลจาก client
if (!name || grade < 1 || grade > 12) return res.status(400).json({ error: "ข้อมูลไม่ถูกต้อง" });
const { rows } = await db.query(
"insert into students (name, grade) values ($1, $2) returning *",
[name, grade],
);
res.status(201).json(rows[0]);
});
app.listen(4000, () => console.log("API on http://localhost:4000"));// 3. Client: React component เรียก API
"use client";
import { useEffect, useState } from "react";
type Student = { id: number; name: string; grade: number };
export default function Students() {
const [students, setStudents] = useState<Student[]>([]);
useEffect(() => {
fetch("http://localhost:4000/api/students")
.then((res) => res.json())
.then(setStudents);
}, []);
return (
<ul>
{students.map((s) => (
<li key={s.id}>{s.name} (Grade {s.grade})</li>
))}
</ul>
);
}$1, $2 คือ parameterized query ส่งค่าแยกจากคำสั่ง SQL กัน SQL injection ห้ามต่อ string เอง เช่น "... values ('" + name + "')"
| ข้อดี | ข้อแลกเปลี่ยน |
|---|---|
| แยก client, API, business logic และ database ชัดเจน | ต้องออกแบบ ดูแล และ deploy backend เอง |
| ควบคุมระบบได้ละเอียด เหมาะกับเรียนพื้นฐาน full-stack | auth และ infrastructure ต้องทำเพิ่มตามโจทย์ |
ลงมือทีละส่วนได้ที่ PostgreSQL, Express และ Challenge Day 3
Nextbase แบบ A–D
Nextbase คือชื่อที่คอร์สนี้ใช้เรียกชุด Next.js + Supabase ไม่ใช่ architecture มาตรฐานแบบเดียว บางระบบอ่านข้อมูลจากเบราว์เซอร์ บางระบบอ่านบน Next.js server และบางฟีเจอร์สร้าง API endpoint เอง ตัวอย่างทุกแบบด้านล่างใช้ client จาก Supabase › สร้าง client
| แบบ | เส้นทางหลัก | เหมาะกับ |
|---|---|---|
| A · Direct Supabase | Browser → Supabase Data API → PostgreSQL | CRUD ฝั่ง client, realtime, dashboard |
| B · Route Handler | Browser → Next.js API → business logic → Supabase | API ให้แอปอื่น, mobile, webhook |
| C1 · Server Component | เปิดหน้า → Server Component → Supabase | อ่านข้อมูลมาสร้างหน้า |
| C2 · Server Action | ส่งฟอร์ม → Server Function → Supabase | เพิ่ม/แก้ข้อมูลผ่านฟอร์ม |
| D · Hybrid | ผสม A/B/C ตามฟีเจอร์ | แอปที่มีหลายรูปแบบการใช้งาน |
- Client Componentsupabase-js
- Supabase Data APIตรวจ JWT และ RLS
- PostgreSQL
// app/live/page.tsx
"use client";
import { useEffect, useState } from "react";
import { supabase } from "@/lib/supabase";
type Student = { id: number; name: string };
export default function LiveStudents() {
const [students, setStudents] = useState<Student[]>([]);
useEffect(() => {
const load = () =>
supabase.from("students").select("id, name").order("id").then(({ data }) => setStudents(data ?? []));
load();
// Realtime: โหลดใหม่ทุกครั้งที่ตาราง students เปลี่ยน
const channel = supabase
.channel("students")
.on("postgres_changes", { event: "*", schema: "public", table: "students" }, load)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, []);
return <ul>{students.map((s) => <li key={s.id}>{s.name}</li>)}</ul>;
}ต้องระวังตารางที่เบราว์เซอร์เข้าถึงต้องเปิด RLS พร้อม policy ที่ถูกต้อง ห้ามส่ง secret key หรือ service role key ไปฝั่ง client และอย่าเชื่อการตรวจกฎสำคัญฝั่ง client อย่างเดียว ส่วน realtime ต้องเปิดให้ตารางนั้นก่อนใน Supabase Dashboard (Database → Publications → supabase_realtime)
- Browser
fetch("/api/students") - Route Handlerauth + business logic
- Supabase
- PostgreSQL
// app/api/students/route.ts
import { supabase } from "@/lib/supabase";
export async function GET() {
const { data, error } = await supabase.from("students").select("id, name, grade");
if (error) return Response.json({ error: error.message }, { status: 500 });
return Response.json(data);
}
export async function POST(request: Request) {
const body = await request.json();
const name = String(body.name ?? "").trim();
const grade = Number(body.grade);
if (!name || !(grade >= 1 && grade <= 12)) {
return Response.json({ error: "ข้อมูลไม่ถูกต้อง" }, { status: 400 });
}
const { data, error } = await supabase.from("students").insert({ name, grade }).select().single();
if (error) return Response.json({ error: error.message }, { status: 500 });
return Response.json(data, { status: 201 });
}// เรียกจากที่ไหนก็ได้: เบราว์เซอร์ แอปมือถือ หรือ Postman
const res = await fetch("/api/students", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Mint", grade: 11 }),
});เหมาะเมื่อต้องการ API ให้ระบบอื่น, integration หรือ webhook ไม่ต้องสร้าง route handler แค่เพื่อให้ Server Component ในโปรเจกต์เดียวกันเรียกข้อมูล
- Browser ขอหน้า
- Server Componentรันบน Next.js server
- Supabase
- HTML ส่งกลับ
// app/students/page.tsx (ไม่มี "use client" = Server Component)
import { supabase } from "@/lib/supabase";
export default async function StudentsPage() {
const { data: students, error } = await supabase
.from("students")
.select("id, name, grade")
.order("name");
if (error) return <p>โหลดไม่สำเร็จ: {error.message}</p>;
return (
<ul>
{students.map((s) => (
<li key={s.id}>{s.name} (ชั้นปีที่ {s.grade})</li>
))}
</ul>
);
}ยังมี HTTP ระหว่างเบราว์เซอร์กับ Next.js และอาจมี Supabase Data API ระหว่าง Next.js กับ Supabase แค่เราไม่ต้องสร้าง API endpoint ของเราเอง
- Submit form
- Server Functionvalidate + auth
- Supabase
- Revalidate UI
// app/students/actions.ts
"use server";
import { revalidatePath } from "next/cache";
import { supabase } from "@/lib/supabase";
export async function addStudent(formData: FormData) {
const name = String(formData.get("name") ?? "").trim();
const grade = Number(formData.get("grade"));
if (!name || !(grade >= 1 && grade <= 12)) throw new Error("ข้อมูลไม่ถูกต้อง");
const { error } = await supabase.from("students").insert({ name, grade });
if (error) throw new Error(error.message);
revalidatePath("/students"); // ให้หน้า /students โหลดข้อมูลใหม่
}// ใน app/students/page.tsx
import { addStudent } from "./actions";
<form action={addStudent}>
<input name="name" placeholder="ชื่อ" required />
<input name="grade" type="number" min={1} max={12} required />
<button>เพิ่มนักเรียน</button>
</form>สำคัญServer Action ถูกเรียกข้ามเครือข่ายได้ ต้องตรวจ authentication, authorization และ validate input ทุกครั้ง การมี "use server" ไม่ได้ทำให้ปลอดภัยเอง
แอปเดียว เลือกเส้นทางตาม use case และใช้ข้อมูลกับกฎร่วมกันได้
| ฟีเจอร์ในระบบโรงเรียน | ใช้แบบ |
|---|---|
| หน้ารายวิชา | C1 · Server Component |
| ฟอร์มบันทึกคะแนน | C2 · Server Action |
| ตารางคะแนนสดตอนสอบ | A · Direct + Realtime |
| API ให้แอปมือถือ หรือ webhook | B · Route Handler |
ยืดหยุ่นที่สุด แต่ไม่ต้องเริ่มแบบนี้ตั้งแต่วันแรก เริ่มจากแบบที่ง่ายกับฟีเจอร์ แล้วค่อยเพิ่มเมื่อมีเหตุผล
คำอธิบายที่มักผิด“Server Component ไม่มี API เลย” ไม่ถูก ที่ถูกคือ Server Component ไม่จำเป็นต้องสร้าง API endpoint ของเราเอง และ Next.js + Supabase ไม่ได้แปลว่า “ไม่มี backend” แค่แบ่งงานระหว่าง server ของเรากับบริการของ Supabase
เลือก architecture
ไม่มีข้อมูลสำรวจที่น่าเชื่อถือพอจะบอกว่าแบบไหน “นิยมที่สุด” ให้เลือกจากลักษณะงาน
| งานที่ต้องทำ | แบบที่ควรพิจารณา | เหตุผล |
|---|---|---|
| หน้าแสดงข้อมูลจาก database | C1 · Server Component | อ่านและ render บน server ไม่ต้องสร้าง API ซ้ำ |
| ฟอร์มเพิ่ม/แก้ข้อมูล | C2 · Server Action | ผูกฟอร์มกับ server logic ได้ตรง ๆ |
| dashboard สด / realtime | A · Direct Supabase | fetch และ subscribe จาก client สะดวก ต้องมี RLS |
| public API / mobile / webhook | B · Route Handler | มี HTTP endpoint ให้ผู้เรียกภายนอก |
| เว็บใหญ่หลายโมดูล | D · Hybrid | ใช้หลายวิธีโดยไม่บังคับทุกอย่างเป็นแบบเดียว |
โปรเจกต์ใหม่บน Next.js App Router ที่อ่านข้อมูลบน server เป็นหลัก เริ่มจาก C1 + C2 แล้วเพิ่ม A หรือ B เมื่อความต้องการชัด
Portfolio ต้องใช้อะไร
ถ้าเว็บมีแค่ About, Skills, Projects, Experience และลิงก์ติดต่อ เริ่มจาก static website (HTML/CSS/JS หรือ Next.js) ไม่ต้องมี Supabase แบบที่ทำใน Day 1 และ Day 2
- ข้อมูลในไฟล์
app/page.tsx, props - Next.js buildได้ HTML/CSS/JS
- Vercelstatic hosting
- Browser
| สิ่งที่ portfolio ต้องทำ | แนวทาง | ทำไม |
|---|---|---|
| About / Projects / ลิงก์ติดต่อ | Next.js แบบ static | ข้อมูลอยู่ในไฟล์ ไม่ต้องมี database |
| เพิ่มผลงานผ่านหน้า admin | Next.js + Supabase | ต้องเก็บข้อมูลถาวรและตรวจสิทธิ์ |
| ฟอร์มส่งอีเมล | บริการฟอร์ม หรือ endpoint ฝั่ง server | ไม่เปิดเผย API secret ในเบราว์เซอร์ |
| ระบบสมาชิก คะแนน ข้อมูลส่วนตัว | Auth + Database + RLS | มีข้อมูลที่ต้องคุมการเข้าถึง |
ประวัติศาสตร์
มี 2 สายคู่ขนาน: สายการสร้าง application และ UI กับสายการจัดการข้อมูล ทั้งสองมาบรรจบกันตอนเราทำ full-stack app อย่าง PERN หรือ Nextbase
- DataEdgar F. Codd: Relational Model
เสนอการเก็บข้อมูลเป็นตารางที่สัมพันธ์กัน เช่น Students, Courses, Enrollments เชื่อมด้วย key และใช้ constraint รักษาความถูกต้อง
- DataIngres ที่ UC Berkeley
โครงการฐานข้อมูลเชิงสัมพันธ์ของกลุ่ม Michael Stonebraker เป็นรากฐานก่อน POSTGRES
- DataPOSTGRES (Post-Ingres)
ต่อยอดจาก Ingres เช่นรองรับชนิดข้อมูลที่ยืดหยุ่นขึ้น เป็นรากของ PostgreSQL
- WebTim Berners-Lee: World Wide Web
เชื่อมเอกสารด้วย URL, HTTP และ HTML เว็บยุคแรกคือเอกสาร คลิกลิงก์ = ขอเอกสารใหม่จาก server
- DataPostgres95 → PostgreSQL
Andrew Yu และ Jolly Chen เพิ่ม SQL ให้ POSTGRES แล้วเปลี่ยนชื่อเป็น PostgreSQL ในปี 1996 เก่ากว่า JavaScript และ React มาก
- WebBrendan Eich: JavaScript ที่ Netscape
ให้เบราว์เซอร์ตอบสนอง event ตรวจฟอร์ม และแก้หน้าเว็บผ่าน DOM ได้ ไม่ได้มาแทน HTML/CSS
- WebXMLHttpRequest และ AJAX
JavaScript ขอข้อมูลแล้วแก้เฉพาะบางส่วนของหน้าโดยไม่ reload ชื่อ AJAX ดังในปี 2005 จาก Gmail และ Google Maps ปัจจุบันนิยมส่ง JSON มากกว่า XML
- ToolsLinus Torvalds สร้าง Git
ระบบ version control แบบกระจาย สำหรับพัฒนา Linux kernel (รายละเอียดใน Git)
- RuntimeGoogle Chrome และ V8
JavaScript engine ที่เร็วขึ้นมาก เขียนด้วย C++ ตัว engine ไม่เท่ากับเบราว์เซอร์ เพราะเบราว์เซอร์ยังมี DOM และ Web API
- ServerRyan Dahl สร้าง Node.js
เอา V8 มารันนอกเบราว์เซอร์ พร้อมระบบ I/O แบบ non-blocking JavaScript จึงรันบน server ได้ Node.js เป็น runtime ไม่ใช่ framework
- ServerExpress.js
framework บน Node.js ที่ทำให้เขียน route, middleware และ response ได้เป็นระบบ เช่น
app.get("/api/students", …) - UIFacebook เปิด React เป็น open source
Jordan Walke และทีมสร้าง UI ด้วย component แบบ declarative เพื่อรับมือ UI ที่ state เปลี่ยนตลอด เช่น feed และ notification
- Full-stackJavaScript everywhere และ PERN
React ทำ frontend, Node/Express ทำ backend, PostgreSQL เก็บข้อมูล PERN เป็นชื่อเรียกชุดเครื่องมือ ไม่ใช่ผลิตภัณฑ์
- FrameworkNext.js
React เป็นแค่ UI library ยังต้องเลือก router, rendering และ data fetching เอง Next.js รวมสิ่งเหล่านี้ไว้ เช่น routing และ server-side rendering
- Backend serviceSupabase
PostgreSQL พร้อม auth, storage, realtime และ API เป็นบริการ ไม่ได้สร้าง database engine ใหม่ เปลี่ยนแค่วิธีจัดการ backend
- Full-stack ReactApp Router และ React Server Components
Next.js 13 เปิด App Router (2022) และประกาศว่าเสถียรใน 13.4 (2023) Server Component ดึงข้อมูลและสร้าง UI บน server โดยไม่ส่ง JavaScript ของตัวเองไปเบราว์เซอร์
เดิม → ใหม่: แต่ละอย่างแก้ปัญหาอะไร
| เดิม | ใหม่ | แก้ปัญหา |
|---|---|---|
| ไฟล์ข้อมูลที่แต่ละโปรแกรมจัดการเอง | Relational database (PostgreSQL) | เชื่อมข้อมูลหลายตารางและรักษาความถูกต้องด้วย SQL, key, constraint, transaction |
| คลิกแล้วโหลดหน้าใหม่ทั้งหน้า | AJAX / fetch | อัปเดตเฉพาะส่วนที่เปลี่ยน (ไม่ได้แปลว่าทุกเว็บต้องเป็น SPA) |
| JavaScript สั่งแก้ DOM ทีละจุด | React | แสดง UI จาก state และ props ดูแล UI ใหญ่ได้ง่ายขึ้น |
| JavaScript รันได้แค่ในเบราว์เซอร์ | Node.js | ใช้ภาษาเดียวกันฝั่ง server (แต่ Node.js ไม่มี DOM) |
| เขียน HTTP server ด้วย Node.js ล้วน | Express | route, middleware, error handling เป็นระบบ |
| React render ทุกอย่างในเบราว์เซอร์ (CSR) | Next.js render บน server (SSR) | เห็นเนื้อหาก่อน แล้วค่อย hydrate (SSR ไม่ได้เร็วกว่าเสมอ) |
| สร้าง API route ทุกครั้งที่ต้องใช้ข้อมูล | Server Component / Server Action | อ่านและเขียนข้อมูลบน server ได้โดยไม่ต้องเขียน REST endpoint เอง |
| PERN: จัดการ backend เองทั้งหมด | Next.js + Supabase | ลดงานตั้งค่า แต่ยังต้องออกแบบ schema, RLS และ business logic เอง |
เล่าให้จบใน 1 ย่อหน้าเว็บเริ่มจากการอ่านเอกสาร → JavaScript เพิ่ม interaction → AJAX ดึงข้อมูลโดยไม่ reload → Node.js ทำให้ JavaScript รันบน server → React จัดการ UI ซับซ้อน → PERN รวม client/API/database → Next.js + Supabase ลดการสร้างระบบพื้นฐานเอง ส่วน PostgreSQL เป็นอีกสายที่เริ่มก่อนทั้งหมด และยังอยู่เบื้องหลัง Supabase
Git ฉบับเข้าใจหลักการ
เคยมีไฟล์ index_final.html, index_final2.html, index_final_REAL.html ไหม พอทำหลายคนหรือแก้บั๊กย้อนหลัง จะไม่รู้ว่าใครแก้อะไรเมื่อไร Git คือ distributed version control system ที่เก็บประวัติของโปรเจกต์เป็นชุด ๆ ที่อ้างถึงได้ ไม่ใช่ภาษาเขียนเว็บ และไม่ใช่บริการฝากโค้ด
- RCS และ CVS
version control ยุคแรก บันทึกประวัติไฟล์ แต่จำกัดเมื่อทีมใหญ่ขึ้น
- Subversion (SVN)
แบบรวมศูนย์: ประวัติหลักอยู่บน server เครื่องเดียว
- Linus Torvalds สร้าง Git
หลังโครงการ Linux kernel เลิกใช้ BitKeeper จึงสร้าง Git ให้ทุกคนมีประวัติครบในเครื่อง แตก branch และ merge ได้เร็ว
- GitHub เปิดตัว
บริการฝาก Git repository พร้อม pull request, issue และ code review
4 พื้นที่ของ Git
- Working directoryไฟล์ที่กำลังแก้
- Staging area
git add - Local repository
git commit - Remote (GitHub)
git push
git init # เริ่ม repo ในโฟลเดอร์ (สร้าง .git)
git status # ดูว่าไฟล์ไหนเปลี่ยน
git add index.html # stage เฉพาะไฟล์นี้
git commit -m "Build homepage" # บันทึกสิ่งที่ stage ไว้เป็น 1 commit
git log --oneline # ดูประวัติ| เข้าใจผิดบ่อย | ที่ถูก |
|---|---|
| Git บันทึกให้อัตโนมัติ | ต้องเลือกเองด้วย git add แล้วสร้าง commit ด้วย git commit |
| commit แล้วเพื่อนเห็น | commit อยู่ในเครื่องเรา ต้อง git push ก่อน |
| push แล้วเว็บเปลี่ยนเสมอ | เว็บเปลี่ยนเมื่อมีการ deploy เช่น Vercel ที่เชื่อม repo ไว้ |
ระวังgit add . เพิ่มทุกการเปลี่ยนแปลงในโฟลเดอร์ ให้เช็ก git status ก่อน commit และมี .gitignore กัน .env, secret key และ node_modules เสมอ
Branch และ merge conflict
Branch คือชื่อที่ชี้ไปยัง commit ล่าสุดของสายงานหนึ่ง ไม่ใช่สำเนาโปรเจกต์ ใช้ทดลองฟีเจอร์โดยไม่กระทบ main
git switch -c feature/navbar # สร้างและย้ายไป branch ใหม่
# ...แก้ Navbar...
git add .
git commit -m "Add responsive navbar"
git switch main
git merge feature/navbar # รวมกลับเข้า mainถ้า 2 branch แก้บรรทัดเดียวกัน Git จะรวมเองไม่ได้และใส่ conflict marker ไว้ในไฟล์
<<<<<<< HEAD
<h1>Hi, I'm Mint</h1>
=======
<h1>Welcome to Mint's portfolio</h1>
>>>>>>> feature/heroเลือกบรรทัดที่ต้องการ (หรือรวมเอง) ลบเครื่องหมาย <<<<<<<, =======, >>>>>>> ออกให้หมด แล้ว git add และ git commit อีกครั้ง
Git ต่างจาก GitHub อย่างไร
| Git | GitHub |
|---|---|
| เครื่องมือ version control ติดตั้งในเครื่อง | บริการฝาก Git repository และทำงานร่วมกัน |
| commit, branch, merge, history | pull request, code review, issue, CI/CD |
| ใช้ได้โดยไม่ต่ออินเทอร์เน็ต | sync และทำงานร่วมกันผ่านออนไลน์ |
| คำสั่ง | ทำอะไร |
|---|---|
git push | ส่ง commit ในเครื่องขึ้น remote |
git fetch | ดึงข้อมูลจาก remote มาดู แต่ยังไม่รวมกับ branch ที่ทำอยู่ |
git pull | fetch แล้วรวมเข้า branch ปัจจุบัน |
git clone | สร้าง repo ในเครื่องจาก remote ที่มีอยู่แล้ว |
Pull request เป็นกระบวนการของ GitHub: แตก branch → push → เปิด PR ให้เพื่อนรีวิวและรัน check → merge เข้า main ลงมือทำได้ที่ GitHub
- Code
- Git commit
- GitHub
git push - Vercelbuild + deploy
- Browserเปิด URL
Workshop 1 คาบ
| เวลา | กิจกรรม | สิ่งที่ได้เรียน |
|---|---|---|
| 10 นาที | สร้าง index.html แล้ว git init | repo คืออะไร |
| 15 นาที | แก้ข้อความ → status → add → commit | working, staging, history |
| 15 นาที | สร้าง feature/about แก้ UI แล้ว merge | branch และ merge |
| 15 นาที | สร้าง repo บน GitHub แล้ว push | local กับ remote |
| 15 นาที | เชื่อม Vercel แล้ว deploy | Git ≠ GitHub ≠ deployment |
git init
git add index.html
git commit -m "Initial portfolio"
git branch -M main
git remote add origin https://github.com/USERNAME/REPO.git
git push -u origin mainคำถามทบทวน
- portfolio ที่มีแค่ About, Projects และลิงก์ติดต่อ ต้องใช้ database ไหม เพราะอะไร
- ทำไมใช้
ProjectCard+.map()แทนการ copy HTML 5 ครั้ง - ทำไมไฟล์
app/projects/page.tsxถึงได้ URL/projects - Server Component กับ Route Handler ต่างกันอย่างไร
- ถ้าใช้แบบ A (Direct Supabase) ต้องเปิดอะไรในตารางก่อน
- commit แล้วแต่ยังไม่ push เพื่อนเห็นโค้ดไหม push แล้วเว็บเปลี่ยนทันทีเสมอหรือเปล่า
- เจอ merge conflict ต้องทำอะไร
คำศัพท์
| คำ | ความหมาย |
|---|---|
| Client | ผู้เรียกบริการ เช่นเบราว์เซอร์หรือแอปมือถือ |
| Server | ระบบที่รับ request แล้วให้บริการหรือประมวลผล |
| API | ช่องทางเรียกความสามารถของระบบ ไม่จำเป็นต้องเป็น HTTP เสมอ |
| API endpoint | จุดรับ HTTP request ที่กำหนด URL และ method เช่น GET /api/students |
| Business logic | กฎของแอป เช่น ใครแก้คะแนนได้ |
| Server Component | React component ที่ทำงานบน server เพื่อสร้าง UI ไม่ใช่ API endpoint |
| Server Action | server function ที่ framework ให้เรียกจาก UI ได้ ต้องตรวจ auth และ validate เสมอ |
| Route Handler | วิธีสร้าง HTTP endpoint เองใน Next.js App Router (route.ts) |
| SSR | การ render HTML บน server ไม่ใช่คำเดียวกับ Server Components |
| RLS | Row Level Security: policy คุมว่าใครอ่าน/เขียนแถวไหนได้ใน PostgreSQL |
| Commit | snapshot ของโปรเจกต์ ณ จุดหนึ่ง พร้อมผู้เขียน เวลา และข้อความ |
| Branch | ชื่อที่ชี้ไปยัง commit ล่าสุดของสายงานหนึ่ง |
ฝึกต่อ
เว็บสำหรับลงมือทำ
วิดีโอเรียนเอง
Next steps
- PERN Stack: ลงมือทำทีละส่วน
- Nextbase: Next.js + Supabase แบบไม่มี server แยก
- Challenge Day 3: สร้าง full-stack web