โหมดมืด
บทที่ 2 — Core Modules + process: ของแถมที่ Node ให้มาฟรี
← บทที่ 1: Modules + npm | สารบัญ | บทที่ 3: Async + Events →
Node มาพร้อม core modules (หรือ built-in modules) — library ที่ติดมากับ Node เลย ไม่ต้อง npm install เช่น fs, http, path, os, crypto, process บทนี้โฟกัสตัวที่ backend ใช้ทุกวัน + global objects ที่มีให้ใช้โดยไม่ต้อง import
หัวข้อหนัก ๆ อย่าง fs/http/stream มีบทของตัวเอง (บท 4–5) — บทนี้ปูพื้น process, path, os, util และ global ก่อน
💡 ของใหม่ที่ Node 22 LTS แถมมา (เจาะลึกในบทถัด ๆ ไป):
node:test(test runner ในตัว ไม่ต้องลง Jest),node:sqlite(SQLite client ในตัว — ตรวจสอบสถานะ/เวอร์ชันล่าสุดในเอกสารทางการก่อนใช้จริง เพราะสถานะ stable ของ module นี้เปลี่ยนไปตามเวอร์ชัน Node),node:stream/web(Web Streams API),util.parseArgs(CLI argument parser ในตัว), Permission Model (--permissionจำกัดสิทธิ์ filesystem/network ของโปรเซส), built-infetch,--watch,--env-file
1. core module คืออะไร และเรียกใช้ยังไง
core module = module ที่ฝังมากับตัว Node — เร็ว, ไม่ต้องโหลด, ไม่ขึ้นกับ npm ของใหม่ใน 2026 ให้ใช้ node: prefix เสมอ (ป้องกันการโดน "หลอก" ด้วย package ใน npm ที่ตั้งชื่อทับซ้อนกับ built-in เช่น มีคนอัปโหลด package ชื่อ fs ขึ้น npm — node:fs รับประกันว่าได้ตัวจริงจาก Node):
javascript
import { readFile } from "node:fs/promises"; // ✅ มาตรฐาน 2026
import path from "node:path";
import os from "node:os";
// เขียนแบบไม่มี node: prefix เป็นของเก่า — ยังใช้ได้แต่ไม่แนะนำในโค้ดใหม่
// import fs from "fs"; ← ใช้ได้ แต่ไม่แนะนำcore module ที่เจอบ่อย:
fs— อ่าน/เขียนไฟล์path— จัดการ path ข้าม OSos— ข้อมูลเครื่อง (CPU, RAM)http/https— ตัว server/client (ใช้ทำ web server, ยิง request)url— parse/ประกอบ URLcrypto— เข้ารหัส, hash, สุ่มเลขevents— EventEmitter (publish/subscribe pattern — บทที่ 3)stream— ส่งข้อมูลทีละชิ้น (บทที่ 5)util— ฟังก์ชันช่วยเหลือ (promisify,inspect,styleText,parseArgs)process— global ไม่ต้อง import (ดูข้อ 3)
2. global objects — ของที่ใช้ได้โดยไม่ต้อง import
ในเบราว์เซอร์ตัวแปร global อยู่ใต้ window — ใน Node อยู่ใต้ global (หรือ globalThis ที่ใช้ได้ทั้งสองที่) ของพวกนี้ใช้ได้เลยไม่ต้อง import:
javascript
console.log("..."); // เหมือนเบราว์เซอร์
setTimeout(fn, 1000); // timers — เหมือนเบราว์เซอร์
setInterval(fn, 1000);
queueMicrotask(fn);
fetch("https://..."); // Node 18+ มี fetch built-in แล้ว (เหมือนเบราว์เซอร์)
structuredClone(obj); // deep clone object
URL, URLSearchParams; // จัดการ URL
// เฉพาะ Node (ไม่มีในเบราว์เซอร์):
process; // ข้อมูล + ควบคุม process (ข้อ 3)
Buffer; // ข้อมูล binary (บทที่ 4)
global; // = window ของฝั่ง Node⚠️ Pitfall: ใน CommonJS มี
__dirname,__filename,require,module,exportsเป็น global-ish ให้ใช้ — แต่ใน ESM ไม่มี (ดูบทที่ 1) ใช้import.meta.dirname/import.meta.urlแทน
3. process — object ที่สำคัญที่สุดของ backend
process คือ global object ที่แทน "process ของ Node ที่กำลังรันอยู่ตอนนี้" — เป็นสะพานเชื่อมโปรแกรมกับโลกภายนอก (ระบบปฏิบัติการ, environment, คนที่สั่งรัน) ใช้บ่อยมากใน backend
3.1 process.env — environment variables (หัวใจของ config)
process.env คือ object ของ environment variable (= ตัวแปรของระบบ/shell ที่ส่งให้โปรเซสตอนรัน) ทั้งหมด — วิธีมาตรฐานในการส่ง config เข้าโปรแกรม (เช่น รหัส DB, port, โหมด) โดยไม่ฮาร์ดโค้ดลงไฟล์ (บทที่ 7 จะลงลึกเรื่อง config):
javascript
// อ่านค่า — ได้ string หรือ undefined เสมอ
console.log(process.env.NODE_ENV); // เช่น 'production' หรือ undefined
console.log(process.env.HOME); // path home (Windows: process.env.USERPROFILE)
// pattern ที่ใช้จริง: อ่าน + ใส่ค่า default
const PORT = process.env.PORT ?? 3000; // ถ้าไม่ตั้งไว้ ใช้ 3000
const isProd = process.env.NODE_ENV === "production";ลองส่ง env ตอนรัน — syntax ต่างกันตาม shell:
bash
# macOS/Linux (bash/zsh): ตั้ง env แบบ inline ผูกกับคำสั่งเดียวเท่านั้น
PORT=8080 NODE_ENV=production node app.jspowershell
# Windows PowerShell: ตั้งใน session ปัจจุบัน (จะค้างอยู่จนปิด terminal)
# $env:NAME = วิธีอ่าน/เขียน environment variable ของ session ใน PowerShell
$env:PORT = "8080"
$env:NODE_ENV = "production"
node app.js
# ถ้าอยากแบบ "ตั้งครั้งเดียวสำหรับคำสั่งนี้" ต้องใช้ scriptblock + Start-Process หรือเครื่องมือเสริม
# วิธีง่ายสุดในปี 2026: ใช้ --env-file (ด้านล่าง)💡 วิธี 2026 ที่แนะนำ: Node 20.6+ มี flag
--env-file=<path>(และ--env-file-if-existsไม่ error ถ้าไม่มีไฟล์) โหลด.envได้โดยตรง ไม่ต้องลงdotenv:bashnode --env-file=.env app.jsในไฟล์
.env:PORT=8080 NODE_ENV=production
⚠️
process.env.Xคืน string เสมอ —process.env.PORTได้"8080"(string) ไม่ใช่8080(number) ถ้าต้องการตัวเลขต้องNumber(process.env.PORT)· ค่าที่ไม่ตั้งไว้คือundefinedไม่ใช่ error
3.2 process.argv — command line arguments
process.argv คือ array ของสิ่งที่พิมพ์ตอนรัน — ใช้ทำ CLI tool (บทที่ 7 ทำ tool เต็ม ๆ):
javascript
// รัน: node app.js hello --verbose
console.log(process.argv);
// [
// '/path/to/node', ← argv[0] = path ของตัว node ที่กำลังรัน
// '/path/to/app.js', ← argv[1] = path ของไฟล์สคริปต์เรา
// 'hello', ← argv[2] = argument ตัวแรกที่ user พิมพ์เอง
// '--verbose' ← argv[3] = argument ตัวที่สอง
// ]
// ทำไมต้องตัด 2 ตัวแรก? เพราะ argv[0] กับ argv[1] เป็น "บริบทการรัน"
// ของ Node เอง ไม่ใช่ของที่ user ตั้งใจส่งให้สคริปต์
const args = process.argv.slice(2); // ตัด 2 ตัวแรกออก เหลือเฉพาะของเรา
console.log(args); // ['hello', '--verbose']💡 วิธี 2026: ถ้าต้อง parse flag (
--name=value,--flag, positional args) ใช้util.parseArgs(Node 18.3+) ที่มีในตัว — เลิกพึ่ง library อย่างyargs/commanderสำหรับงานเล็ก ๆ (ดูบทที่ 7)
3.3 process.exit + exit code
process.exit(code) สั่งจบโปรแกรมทันที — 0 = สำเร็จ, ตัวเลขอื่น = ผิดพลาด ค่านี้เรียกว่า exit code และ shell กับเครื่องมืออัตโนมัติใช้มันตัดสินว่าคำสั่งสำเร็จไหม เช่น && ของ bash จะรันคำสั่งถัดไปก็ต่อเมื่อ exit code = 0 เท่านั้น CI (ย่อจาก Continuous Integration) คือระบบที่รันคำสั่ง build/test ให้อัตโนมัติทุกครั้งที่มีการ push โค้ด — มันก็อาศัย exit code นี้เป็นตัวตัดสินเช่นกันว่างานผ่านหรือไม่ผ่าน:
javascript
if (!process.env.DATABASE_URL) {
console.error("❌ ต้องตั้ง DATABASE_URL");
process.exit(1); // จบด้วย exit code 1 = error
}⚠️ Pitfall:
process.exit()จบ ทันที — งาน async ที่ค้างอยู่ (เขียนไฟล์/log ที่ยังไม่เสร็จ) จะถูกตัดกลางคัน ในกรณีปกติ อย่าเรียกprocess.exit()เอง ปล่อยให้โปรแกรมจบเองเมื่อไม่มีงานค้าง (event loop ว่าง) — ใช้process.exit()เฉพาะตอน fatal error จริง ๆ เท่านั้น
3.4 process อื่น ๆ ที่เจอบ่อย
javascript
process.cwd(); // current working directory — โฟลเดอร์ที่ "รันคำสั่ง" (ไม่ใช่ที่อยู่ไฟล์!)
process.pid; // process id — เลขประจำตัว process
process.platform; // 'darwin' | 'win32' | 'linux'
process.version; // เวอร์ชัน Node เช่น 'v22.1.0'
process.uptime(); // โปรแกรมรันมากี่วินาทีแล้ว
process.memoryUsage();// การใช้ memory (ดู heap — บทที่ 10)⚠️ จุดพลาดคลาสสิก: สับสนระหว่าง
process.cwd()(โฟลเดอร์ที่ "รันคำสั่ง") กับimport.meta.dirname(โฟลเดอร์ที่ "ไฟล์อยู่") — ถ้ารันnode src/app.jsจากราก:process.cwd()= ราก แต่import.meta.dirname=.../srcใช้ผิดตัวจะหาไฟล์ไม่เจอ
3.5 process events — ดักจังหวะสำคัญ
process ยิง event ตอนเหตุการณ์สำคัญเกิดขึ้น — สำคัญมากตอนทำ graceful shutdown (= ปิดโปรแกรมอย่างเรียบร้อย ไม่ใช่ตายดิบทันที) หมายถึงก่อนโปรแกรมจะจบ ต้องปิด connection กับ DB ให้เรียบร้อย ปิด server ไม่ให้รับ request ใหม่ และรอส่ง response ที่ค้างอยู่ให้เสร็จก่อน (บทที่ 7 อธิบายเต็ม) เรื่องนี้เกี่ยวโยงกับ error handling ด้วย (บทที่ 6):
javascript
// ดักสัญญาณปิดโปรแกรม เพื่อปิดให้เรียบร้อยก่อนตาย
// SIGTERM = signal มาตรฐาน "ขอให้ปิดอย่างสุภาพ" ที่ระบบยิงให้โปรเซส
// เช่น เวลา `docker stop` (สั่งให้ container หยุด), kubernetes rolling deploy, systemctl stop
// ส่วน Ctrl+C ในเทอร์มินัลจะยิง SIGINT (เป็น event แยก แต่ดักลักษณะเดียวกัน)
process.on("SIGTERM", () => {
console.log("กำลังปิด server อย่างนุ่มนวล...");
// ปิด DB, ปิด server, แล้วค่อยจบ
});
// ดัก error ที่หลุดมาไม่มีใครจับ (ไม้ตายสุดท้าย — บทที่ 6 อธิบาย)
// ⚠️ ทำไมต้อง exit(1) ไม่พยายาม "เก็บกวาดแล้วทำงานต่อ"?
// เพราะ uncaughtException แปลว่า "มีอะไรพังถึงระดับที่หลุดมาถึงตรงนี้" —
// state ภายในของโปรแกรม (ตัวแปร, connection, GC) อาจเสียหายไม่สามารถวางใจได้
// วิธีที่ปลอดภัยคือ log แล้วจบทันที ให้ตัว orchestrator (docker/k8s/pm2) restart ใหม่
process.on("uncaughtException", (err) => {
console.error("error ที่ไม่ได้จับ:", err);
process.exit(1);
});
// คู่กับ uncaughtException: ดัก Promise ที่ reject แล้วไม่มีใคร .catch()
// ตั้งแต่ Node 15+ default = terminate อยู่แล้ว แต่ใส่ handler เพื่อ log
process.on("unhandledRejection", (reason) => {
console.error("Promise ที่ reject โดยไม่มีใครจับ:", reason);
process.exit(1);
});4. path — จัดการ path ให้ถูกทุก OS
อย่าต่อ path ด้วยการเอา string มาบวก "folder" + "/" + "file" — เพราะ Windows ใช้ \ แต่ macOS/Linux ใช้ / โค้ดจะพังข้ามเครื่อง ใช้ path เสมอ มันเลือกตัวคั่นให้ถูกตาม OS:
javascript
import path from "node:path";
// join — ต่อ path อย่างถูกต้อง (ใส่ตัวคั่นให้เอง)
path.join("/users", "somchai", "data.json");
// macOS/Linux → '/users/somchai/data.json'
// Windows → '\users\somchai\data.json'
// resolve — แปลงเป็น absolute path (อิงจาก cwd ถ้าไม่ใช่ absolute)
path.resolve("src", "app.js"); // → '/current/dir/src/app.js'
// แยกส่วนของ path
path.basename("/a/b/file.txt"); // 'file.txt' ← ชื่อไฟล์
path.dirname("/a/b/file.txt"); // '/a/b' ← โฟลเดอร์
path.extname("/a/b/file.txt"); // '.txt' ← นามสกุล
path.parse("/a/b/file.txt"); // { dir, base, name: 'file', ext: '.txt' }
// pattern ใช้จริง: หาไฟล์ที่อยู่ "ข้าง ๆ" ไฟล์โค้ดเรา (ESM)
const configPath = path.join(import.meta.dirname, "config.json");💡 จำง่าย:
join= ต่อชิ้นส่วน path ·resolve= ทำให้เป็น absolute · ใช้import.meta.dirnameเป็นจุดอ้างอิงเมื่ออยากชี้ไฟล์ที่อยู่ "ข้างโค้ด" และprocess.cwd()เมื่ออยากอิง "ที่ user รันคำสั่ง"
5. os — ข้อมูลเครื่องที่รันอยู่
os บอกข้อมูลฮาร์ดแวร์/ระบบ — ใช้ตอน optimize หรือ health check เช่น เปิด worker หลายตัวให้เท่าจำนวน CPU core (worker = โปรเซสลูกที่รันคู่ขนานกันเพื่อแบ่งโหลดงาน ส่วน cluster คือกลไกของ Node ที่ช่วยสร้าง/จัดการ worker หลายตัวเหล่านี้ให้อัตโนมัติ — รายละเอียดเต็มดูบทที่ 10):
javascript
import os from "node:os";
os.cpus().length; // จำนวน CPU core เช่น 8 → ใช้ตั้งจำนวน worker/cluster
os.totalmem(); // RAM ทั้งหมด (bytes)
os.freemem(); // RAM ว่าง (bytes)
os.hostname(); // ชื่อเครื่อง
os.platform(); // เหมือน process.platform
os.tmpdir(); // โฟลเดอร์ temp ของระบบ (ที่เขียนไฟล์ชั่วคราวได้)
os.homedir(); // home directory ของ user6. util — เครื่องมือสารพัดประโยชน์
util มีฟังก์ชันช่วยเหลือหลายอย่าง — ตัวที่ใช้บ่อยสุดคือ promisify (แปลง callback API เก่าให้เป็น Promise) ตัวอย่างข้างล่างมีการใช้ await ซึ่งเป็นเรื่องของ Promise ที่บทที่ 3 จะอธิบายเต็ม ๆ — ตอนนี้ยังไม่ต้องเข้าใจลึก แค่ทำตามตัวอย่างและรู้ว่ามันแปลง callback ให้เขียนแบบ await ได้ก็พอ:
javascript
import util from "node:util";
// inspect — แปลง object เป็น string สวย ๆ สำหรับ debug (เจาะ nested object ลึก ๆ)
const obj = { a: { b: { c: { d: 1 } } } };
console.log(util.inspect(obj, { depth: null, colors: true })); // เห็นครบทุกชั้น
// promisify.mjs — ต้องเป็นไฟล์ ESM (.mjs หรือมี "type": "module") เพราะมี top-level await ข้างล่าง
// promisify — แปลง function แบบ callback ให้เป็นแบบ Promise (บทที่ 3 อธิบาย Promise/await เต็ม)
import { readFile } from "node:fs";
const readFileAsync = util.promisify(readFile);
// ⚠️ บรรทัดถัดไปใช้ top-level await — ต้องอยู่ในไฟล์ ESM เท่านั้น
// (มี "type": "module" ใน package.json หรือไฟล์ .mjs — ดูบทที่ 1)
const data = await readFileAsync("file.txt", "utf8");
// styleText — ใส่สีให้ข้อความใน terminal โดยไม่ต้องลง library (เช่น chalk)
// เพิ่มเข้ามาตั้งแต่ Node 20.12 / 21.7 — ใน Node 22 LTS ใช้งานได้ stable แล้ว
console.log(util.styleText("green", "✅ สำเร็จ"));
// parseArgs (Node 18.3+) — parse CLI flag ในตัว ทดแทน yargs/commander สำหรับงานเล็ก
const { values, positionals } = util.parseArgs({
options: { verbose: { type: "boolean", short: "v" } },
allowPositionals: true,
});
// node app.js hello -v → values = { verbose: true }, positionals = ['hello']7. ตัวอย่างรวม — สคริปต์ที่ใช้หลาย core module
เอามาประกอบกันเป็นสคริปต์จริง — รายงานข้อมูลระบบออกไฟล์:
javascript
// sysreport.mjs — รายงานข้อมูลเครื่องลงไฟล์ JSON
import os from "node:os";
import path from "node:path";
import { writeFile } from "node:fs/promises";
// 1. รวบรวมข้อมูลจาก os + process
const report = {
hostname: os.hostname(),
platform: process.platform,
nodeVersion: process.version,
cpuCount: os.cpus().length,
memoryGB: (os.totalmem() / 1024 ** 3).toFixed(1), // bytes → GB
cwd: process.cwd(),
generatedAt: new Date().toISOString(),
};
// 2. อ่านชื่อไฟล์ output จาก argument (ถ้าไม่ส่ง ใช้ default)
const outName = process.argv[2] ?? "report.json";
// 3. สร้าง path ที่ถูกต้องข้าง ๆ ไฟล์สคริปต์นี้ แล้วเขียนไฟล์
const outPath = path.join(import.meta.dirname, outName);
await writeFile(outPath, JSON.stringify(report, null, 2)); // null,2 = จัดรูปสวย
console.log(`✅ เขียนรายงานที่ ${outPath}`);bash
$ node sysreport.mjs
✅ เขียนรายงานที่ /path/report.json
$ node sysreport.mjs myreport.json # ส่งชื่อไฟล์เอง🛠️ Checkpoint 2 — ลงมือก่อนไปบทถัดไป
- เขียนสคริปต์ที่อ่าน
process.argvรับตัวเลข 2 ตัวแล้วพิมพ์ผลบวก เช่นnode sum.js 3 4→7(ระวัง: argv เป็น string ต้องNumber()) - เขียนสคริปต์ที่ตรวจว่า
process.env.API_KEYถูกตั้งไหม — ถ้าไม่ พิมพ์ error แล้วprocess.exit(1)· ลองรันทั้งแบบมีและไม่มี env แล้วเช็ก exit code ด้วยecho $?(macOS/Linux bash) หรือecho $LASTEXITCODE(Windows PowerShell — ในcmd.exeใช้echo %ERRORLEVEL%) - ใช้
pathแยก/var/log/app/server.logออกเป็น dirname / basename / extname - เขียนสคริปต์
sysinfoที่พิมพ์จำนวน CPU, RAM ว่างเป็น GB, และ uptime ของโปรแกรม - ลองสร้างไฟล์ใน
src/app.jsที่console.log(process.cwd())และconsole.log(import.meta.dirname)แล้วรันจากโฟลเดอร์ราก (node src/app.js) — สังเกตว่าได้ค่าต่างกัน เข้าใจว่าทำไม
เฉลยข้อ 1:
javascriptconst [a, b] = process.argv.slice(2).map(Number); // แปลง string → number console.log(a + b);
สรุปบทที่ 2
- core module = built-in มากับ Node ไม่ต้อง install — เรียกด้วย
node:prefix (เช่นnode:fs) - global ใช้ได้เลยไม่ต้อง import:
console,setTimeout,fetch,process,Buffer,globalThis process= สะพานเชื่อมโปรแกรมกับโลกภายนอก:process.env= config ผ่าน environment (คืน string เสมอ)process.argv= command line argumentsprocess.exit(code)= จบโปรแกรม (0สำเร็จ) — เลี่ยงเรียกเองนอกจาก fatalprocess.on("SIGTERM"/"uncaughtException")= ดักจังหวะปิด/error
pathจัดการ path ข้าม OS —join/resolve/basename/dirname· อย่าต่อ path ด้วย string บวกกันเองosข้อมูลเครื่อง (CPU, RAM) ·utilเครื่องมือ (promisify,inspect)- ระวังคู่สับสน:
process.cwd()(ที่รันคำสั่ง) ≠import.meta.dirname(ที่ไฟล์อยู่)
บทต่อไปคือหัวใจของ Node — async, callback, Promise, EventEmitter และ event loop เชิงลึก ถ้าผ่านบทนี้ได้ ที่เหลือสบาย