ข้ามไปยังเนื้อหา

Repo Layout

repository เดียวชื่อ devblog/ ที่เก็บทั้งสองแอปและโครงสร้างพื้นฐานที่ใช้ร่วมกัน:

devblog/
├── apps/
│ ├── api/ # NestJS GraphQL API (scaffolded in the next lesson)
│ └── web/ # Next.js App Router front end (scaffolded in Lesson 3)
├── infra/ # Docker Compose files for local development
├── .env # local secrets — never committed
├── .env.example # committed template, documents every variable
└── .gitignore

บทเรียนนี้สร้างโครงกับไฟล์ระดับ root สองไฟล์ ส่วน apps/api และ apps/web จะให้ CLI ของแต่ละฝั่ง scaffold เองในสองบทเรียนถัดไป ตรงนี้เราจึงสร้างแค่โฟลเดอร์แม่ไว้ก่อน

ทุกโมดูลถัดไปแก้ทั้ง apps/api และ apps/web และทั้งสองฝั่งต้องยึด contract ชุดเดียวกัน: JWT secret, connection string ของ Mongo และ URL ของ API ที่ฝั่ง frontend เรียก การเก็บทั้งคู่ไว้ใน repository เดียวพร้อม root .env ไฟล์เดียว ทำให้ contract นั้นเบี่ยงเบนไม่ได้ มีไฟล์เดียวที่ต้องอัปเดตเมื่อค่าเปลี่ยน และมี git log เดียวที่บอกว่า API กับ UI พัฒนาไปด้วยกันอย่างไร

สิ่งที่ monorepo ให้คุณตรงนี้ .env และ .gitignore ไฟล์เดียวที่ใช้ร่วมกันทั้งสองแอป infra/docker-compose.yml ไฟล์เดียวที่ orchestrate ฐานข้อมูล API และเว็บแอปพร้อมกัน commit แบบ atomic — แก้ field ใน GraphQL พร้อม query ฝั่ง frontend ที่อ่าน field นั้นได้ใน commit เดียว ไม่ใช่สอง pull request ที่ต้อง merge ให้ถูกลำดับ และ git clone ครั้งเดียวผู้ร่วมงานใหม่ก็ได้ระบบทั้งหมด

สิ่งที่ต้องแลก release ของสองแอปผูกอยู่กับประวัติ repository เดียวกัน ทั้งที่โดยหลักการ deploy แยกกันได้ ประวัติ git ก็ปนกันระหว่างเรื่องของ API กับ UI และ CI ต้องฉลาดเรื่อง path ด้วย (แก้ใน apps/web/ ไม่ควรลาม rebuild apps/api/) ไม่งั้นจะเสียเวลา rebuild ทุกอย่างทุก commit

ทางเลือกอื่น — polyrepo แยก repository ของ api, web และ infra ออกจากกัน แต่ละตัวได้ versioning อิสระ CI pipeline ของตัวเอง และขอบเขตความเป็นเจ้าของที่ชัดเจนถ้าคนละทีมดูแลคนละส่วน ต้นทุนคือสิ่งที่ monorepo ให้พอดี: การรักษา contract JWT/env ให้ตรงกันจะต้องประสานการเปลี่ยนแปลงข้าม repository และไม่มีคำสั่งเดียวที่ยกทั้งสแตกขึ้นมาให้ผู้ร่วมงานใหม่

สำหรับโปรเจกต์สองแอปที่ใช้ contract API เดียวกันและ workflow dev ในเครื่องชุดเดียวกัน monorepo คือทางเลือกที่ถูกต้อง DevBlog เลือกแบบนี้

สร้างโฟลเดอร์ระดับบนสุดและเริ่ม git:

Terminal window
mkdir -p devblog/apps devblog/infra
cd devblog
git init

สร้าง .gitignore ที่ root ของ repo:

node_modules/
dist/
build/
.next/
out/
coverage/
*.log
.DS_Store
.env
.env.local

.env.example ตั้งใจไม่อยู่ในลิสต์นี้ เพราะไม่มีความลับอยู่ในนั้นเลย และต้อง track ไว้เพื่อให้ผู้ร่วมงานทุกคนรู้ว่าต้องตั้งตัวแปรอะไรบ้าง

สร้าง .env.example ที่ root ของ repo พร้อมตัวแปรทุกตัวที่สแตกต้องการ:

MONGODB_URI=mongodb://devblog:devblog@localhost:27017/devblog?authSource=admin
JWT_SECRET=change-me-in-prod
API_PORT=4000
WEB_ORIGIN=http://localhost:3000
NEXT_PUBLIC_API_URL=http://localhost:4000/graphql

แต่ละตัวแปรมีไว้ทำอะไร:

  • MONGODB_URI — connection string ที่ Mongoose ใช้เชื่อมต่อ MongoDB credential devblog:devblog และ authSource=admin ตรงกับ root user ที่คุณจะสร้างให้คอนเทนเนอร์ Mongo ใน Compose skeleton
  • JWT_SECRET — secret ที่ Passport-JWT ใช้เซ็นและตรวจสอบ auth token ของผู้เขียน change-me-in-prod เป็นแค่ placeholder; การ deploy จริงต้อง override ด้วยค่าสุ่มยาว ๆ
  • API_PORT — พอร์ตที่ NestJS API เปิดฟัง อ่านผ่าน @nestjs/config ใน main.ts ดูรายละเอียดที่ Backend init
  • WEB_ORIGIN — origin ที่ API ยอมให้เรียกเข้ามา (ก็คือแอป Next.js) ใช้ตอนตั้งค่า CORS ในโมดูลถัดไป
  • NEXT_PUBLIC_API_URL — GraphQL endpoint ที่แอป Next.js เรียก คำนำหน้า NEXT_PUBLIC_ เป็นข้อตกลงของ Next.js ที่ทำให้โค้ดฝั่ง client อ่านตัวแปรนี้ได้ด้วย ไม่ใช่แค่ฝั่ง server

สุดท้าย ก๊อป template ไปเป็น .env ตัวจริงที่ติด gitignore ไฟล์นี้แหละที่ process ในเครื่องคุณอ่านจริง ๆ:

Terminal window
cp .env.example .env
Terminal window
ls -la devblog
# .env .env.example .gitignore apps infra .git
cat devblog/.env.example
# (prints the five variables above)
cd devblog && git status
# .env is not listed (ignored); .env.example, .gitignore are untracked and ready to commit

ถ้า .env ไม่ ปรากฏใน git status แต่ .env.example ปรากฏ แสดงว่า .gitignore ของคุณทำงานถูกต้อง

devblog/ เป็น monorepo ที่มี apps/api, apps/web และ infra/ เป็นพี่น้องกัน พร้อม root .gitignore และ .env.example การเลือก monorepo คือการแลก deployment อิสระต่อแอป กับ single source of truth ของ contract JWT/Mongo/API-URL ที่ทั้งสองแอปใช้ร่วมกัน ที่เป็นการตัดสินใจที่ถูกต้องสำหรับสองแอปที่สร้างไปด้วยกัน ตัว .env.example ทำหน้าที่เป็นเอกสารของตัวแปรทุกตัว ส่วน .env จริงเก็บความลับไว้ในเครื่องและไม่ commit ขึ้นไปเลย

ถัดไป: Backend init →