Skip to content

บทที่ 11 — Production + Modern Node: เอาขึ้นจริง + ก้าวต่อไป

← บทที่ 10: Performance + Scaling | สารบัญ

บทสุดท้าย — เปลี่ยน "โค้ดที่รันในเครื่องเรา" ให้เป็น "บริการที่รันบน production ได้จริง 24 ชั่วโมง" แล้วปิดท้ายด้วยแผนที่ก้าวต่อไป:

  1. Docker สำหรับ Node — image ที่เล็ก ปลอดภัย เร็ว
  2. Process manager (PM2) + การรันใน container
  3. Health check + structured logging (pino)
  4. Security checklist — กับดักที่ทำระบบโดนแฮก
  5. Modern Node — ฟีเจอร์ใหม่ที่ควรรู้ + TypeScript กับ Node + Deno/Bun
  6. Roadmap — ไปต่อที่ไหน (NestJS, Next.js, ...)

1. Docker สำหรับ Node — image เล็กและปลอดภัย

Production ส่วนใหญ่รัน Node ใน container (ดู Docker) จุดสำคัญคือทำ image ให้เล็ก + ปลอดภัย ด้วย multi-stage build:

📘 คำศัพท์ Dockerfile แบบเร็ว ๆ (รายละเอียดที่ Docker):

  • FROM ... AS <name> = เริ่ม stage ใหม่จาก image ฐาน + ตั้งชื่อให้อ้างถึงได้
  • WORKDIR = cd เข้า folder นั้น (ถ้าไม่มีจะสร้างให้)
  • COPY --chown=user:group src dest = copy ไฟล์เข้า image แล้วเปลี่ยนเจ้าของ
  • EXPOSE = บอกว่า container เปิด port อะไร (เอกสารเฉย ๆ — ไม่ได้เปิดให้จริง)
  • CMD = คำสั่งที่ container รันตอน start
dockerfile
# Dockerfile — multi-stage: stage แรก build, stage สองเอาเฉพาะของจำเป็น
# (ตัวอย่างนี้สำหรับ pure-JS — ถ้าเป็น TypeScript ดู stage 'build' ด้านล่าง)

# ---- stage 1: ติดตั้ง dependencies ----
# 💡 production แนะนำ pin ด้วย digest เช่น node:22-slim@sha256:... เพื่อกัน image เปลี่ยน
FROM node:22-slim AS deps
WORKDIR /app
COPY package*.json ./
# npm ci = ติดตั้งจาก lockfile เป๊ะ (บทที่ 1) · --omit=dev = ไม่เอา devDependencies → เล็กลง
RUN npm ci --omit=dev

# ---- stage 2: image จริงที่ deploy (เล็ก ไม่มีเครื่องมือ build) ----
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
# ⭐ รันด้วย user ที่ไม่ใช่ root (image node มี user 'node' ให้แล้ว) — ปลอดภัยขึ้น
USER node
COPY --chown=node:node --from=deps /app/node_modules ./node_modules
COPY --chown=node:node . .
EXPOSE 3000
# HEALTHCHECK = ให้ Docker เช็กว่า container ยังไหวไหม (ใน K8s ใช้ probe แทน — ข้อ 3)
# บรรทัดล่างนี้คือ: เรียก /healthz → ถ้าตอบ ok ก็ exit(0) แปลว่า healthy
#                                  → ถ้าไม่ตอบ/error ก็ exit(1) แปลว่า unhealthy
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD node -e "fetch('http://localhost:3000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
# รัน node ตรง ๆ (ไม่ผ่าน npm) เพื่อให้ signal SIGTERM ส่งถึง process จริง (บทที่ 7)
# ไม่ต้องใช้ dumb-init/tini เพราะแอปนี้ไม่ spawn child process ที่ต้องการ zombie reaping
# และ CMD ใช้ exec form (ไม่ใช่ shell form) — signal จึงส่งถึง Node ตรง ๆ อยู่แล้ว
# (ถ้าแอป spawn subprocess ด้วย execFile/spawn — บทที่ 10 — ให้พิจารณาใช้ tini อยู่ดี)
CMD ["node", "server.mjs"]

💡 TypeScript / มี build step? ใช้ 3 stage: deps (npm ci ทุก dep รวม dev), build (tsc หรือ esbuild → dist/), runtime (npm ci --omit=dev + copy dist/ จาก stage build) — stage runtime จะไม่มี TypeScript compiler ติดมาด้วย image จะเล็กและปลอดภัยกว่า

bash
# .dockerignore — อย่า copy ของพวกนี้เข้า image (สำคัญ!)
node_modules
.env
.git
*.log

จุดที่มือใหม่พลาดบ่อย:

  • COPY package*.json ก่อน COPY . . — เพื่อให้ Docker cache ชั้น npm ci ไว้ (โค้ดเปลี่ยนแต่ dependency ไม่เปลี่ยน → ไม่ต้องติดตั้งใหม่ build เร็วขึ้นมาก)
  • node:22-slim (หรือ alpine) แทน node:22 เต็ม — เล็กกว่าหลายร้อย MB
  • --omit=dev — ไม่เอา devDependencies ขึ้น production
  • ใส่ .env ใน .dockerignore — ไม่ฝังความลับใน image (ส่ง env ตอน run แทน — บทที่ 7)

⚠️ Pitfall: ใช้ CMD ["npm", "start"] แทน CMD ["node", "server.mjs"] → npm กลายเป็น process หลัก (PID 1) และ ไม่ส่งต่อ SIGTERM ให้ node → graceful shutdown (บทที่ 7) ไม่ทำงาน · รัน node ตรง ๆ


2. Process manager + การรันใน container

โปรแกรมต้อง "ฟื้นเองเมื่อ crash" และใช้ทุก CPU core (บทที่ 10) — มี 2 แนวทางตามที่ deploy:

บน VM/เครื่องเปล่า → PM2:

bash
npm install -g pm2
pm2 start server.mjs -i max     # -i max = cluster ตามจำนวน core (บทที่ 10)
pm2 startup && pm2 save          # ให้ start เองตอนเครื่อง boot
pm2 logs                         # ดู log

PM2 จัดการ restart-on-crash, cluster, log rotation, zero-downtime reload ให้

ใน container/Kubernetes → ไม่ต้องใช้ PM2:

📘 คำศัพท์เร็ว ๆ (ลึกขึ้นที่ Kubernetes):

  • K8s / Kubernetes = ระบบ orchestrate container — สั่งรัน/restart/scale ให้อัตโนมัติ
  • replica = สำเนา container ที่รันคู่ขนาน (เช่น 3 replica = 3 instance)
  • container = process ที่รันแยกใน sandbox (Docker = container ที่ใช้บ่อยสุด)
  • signal = ข้อความ OS ส่งให้ process (เช่น SIGTERM = ขอให้ปิดสวย ๆ, SIGKILL = บังคับฆ่า)
  • Kubernetes ทำหน้าที่ restart + scale ให้อยู่แล้ว → รัน node ตรง ๆ 1 process ต่อ container แล้วให้ K8s รันหลาย replica (1 process ต่อ container = หลักการ "1 process 1 container")
  • ใส่ PM2 ซ้อนใน container = ซ้ำซ้อน + ทำให้ K8s health check / signal ยุ่ง

ตัวอย่าง K8s manifest พร้อม probe + graceful shutdown

yaml
# deployment.yaml — ของจริง production ต้องมีครบทุกอันนี้
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  template:
    spec:
      # ⭐ ให้ K8s รอ N วินาทีให้ pod ปิดสวย ๆ ก่อนฆ่า (ค่า default 30s มักไม่พอ)
      terminationGracePeriodSeconds: 45
      containers:
      - name: api
        image: myapp:1.2.3
        ports:
        - containerPort: 3000
        lifecycle:
          preStop:
            # หน่วง 10s ก่อน SIGTERM — ให้ LB ถอด pod ออกจาก endpoint ก่อน
            # ไม่งั้น request ที่เข้ามาช่วงนั้นจะเจอ connection refused
            exec:
              command: ["sh", "-c", "sleep 10"]
        # startup probe = ให้เวลา app boot (รันก่อน liveness/readiness)
        startupProbe:
          httpGet: { path: /healthz, port: 3000 }
          failureThreshold: 30   # 30 × 2s = ให้ boot ได้ไม่เกิน 60s
          periodSeconds: 2
        # liveness = ตัวเองตายค้างไหม → restart
        livenessProbe:
          httpGet: { path: /healthz, port: 3000 }
          periodSeconds: 10
        # readiness = พร้อมรับ traffic ไหม → ใส่/ถอดจาก LB
        readinessProbe:
          httpGet: { path: /readyz, port: 3000 }
          periodSeconds: 5
---
# pdb.yaml — PodDisruptionBudget: กัน K8s ฆ่า pod เยอะเกินตอน maintenance
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  minAvailable: 2          # มีอย่างน้อย 2 pod online เสมอ
  selector:
    matchLabels:
      app: api

💡 ทำไมต้อง preStop sleep: (Service = ตัวกลางใน K8s ที่กระจาย request ไปยัง pod ที่ยัง online อยู่ — ไม่ใช่ pod เอง)

  1. ตอน K8s จะปิด pod มันทำ 2 อย่างพร้อมกัน: ส่ง SIGTERM ให้ container และถอด pod ออกจาก Service (ให้ traffic ไม่วิ่งมาที่ pod นี้อีก)
  2. สองอย่างนี้ไม่ sync กัน ทำให้ช่วง ~5–10 วินาทีแรก load balancer (LB) อาจยังส่ง traffic มาทั้งที่ Node ปิดไปแล้ว
  3. preStop sleep ช่วยหน่วงเวลาก่อนส่ง SIGTERM จริง เพื่อให้ LB ถอด pod ออกก่อน

3. Health check + Structured logging

Health check — ให้ระบบรู้ว่าเรายังไหวไหม

Load balancer / Kubernetes ต้องรู้ว่า instance ไหน "พร้อมรับงาน" — ผ่าน health endpoint:

javascript
// liveness: process ยังไม่ตายค้างไหม (ตอบเร็ว ๆ ไม่เช็ก dependency)
app.get("/healthz", (req, res) => res.json({ status: "ok" }));

// readiness: พร้อมรับ traffic ไหม (เช็ก dependency เช่น DB ต่อได้ไหม)
let isReady = false;     // ⭐ เพิ่ง boot อย่าเพิ่งบอก ready — รอ warm-up ก่อน (set true หลัง init เสร็จ)

app.get("/readyz", async (req, res) => {
  if (!isReady) return res.status(503).json({ status: "warming up" });
  try {
    // ⭐ ใส่ timeout — ไม่งั้น DB ค้างจะทำ readiness ค้างตามไปด้วย → LB งง
    await Promise.race([
      pool.query("SELECT 1"),   // เช็ก DB (บทที่ 9)
      new Promise((_, rej) => setTimeout(() => rej(new Error("db timeout")), 2000)),
    ]);
    res.json({ status: "ready" });
  } catch {
    res.status(503).json({ status: "not ready" });   // DB ล่ม → บอกว่ายังไม่พร้อม → LB ไม่ส่ง traffic มา
  }
});

// ตอน boot เสร็จเปิด ready
app.listen(3000, () => { isReady = true; logger.info("ready"); });

💡 แยก liveness (ตัวเองตายไหม → ถ้าตาย restart) จาก readiness (พร้อมรับงานไหม → ถ้าไม่ ตัดออกจาก pool ชั่วคราว) — รายละเอียดที่ Kubernetes บทที่ 7

Structured logging ด้วย pino

console.log ใช้ debug ตอน dev ได้ แต่ production ต้อง log เป็น JSON (มี level, timestamp, context) เพื่อให้ระบบเก็บ log ค้นหา/กรองได้ — pino เป็น default ที่แนะนำสำหรับ Node ปี 2026 (เร็วที่สุด, JSON ตั้งแต่ default, มี ecosystem ครบ)

📘 คำศัพท์: Loki / Elasticsearch = ระบบเก็บ + ค้นหา log แบบกลาง (ส่ง log จากทุก service ไปกองรวมกัน แล้วค้นได้เหมือน Google) — รายละเอียดที่ Observability

bash
npm install pino pino-http        # pino-http = middleware log request/response ให้ Express
javascript
import pino from "pino";
import pinoHttp from "pino-http";

const logger = pino({
  level: process.env.LOG_LEVEL ?? "info",
  // serializers = แปลง object ก้อนใหญ่ให้เป็นรูปอ่านง่าย+ปลอดภัย (ไม่ leak field ที่ไม่ควร log)
  serializers: {
    err: pino.stdSerializers.err,
    req: pino.stdSerializers.req,
    res: pino.stdSerializers.res,
  },
  // dev: อ่านง่ายมีสี (pino-pretty) · production: JSON ดิบ ๆ
});

// ใส่ middleware → ทุก request จะมี log อัตโนมัติ (พร้อม request id, latency)
app.use(pinoHttp({ logger }));

logger.info({ userId: 42, action: "login" }, "user เข้าสู่ระบบ");
// production output (JSON): {"level":30,"time":...,"userId":42,"action":"login","msg":"user เข้าสู่ระบบ"}

logger.error({ err }, "query พัง");   // log error พร้อม context (stack trace ครบจาก serializer)

ทำไม JSON: ระบบ log จะ index field (userId, action) ให้ค้นได้ — เช่น "หา log ทั้งหมดของ userId 42 ที่ level error" · ส่วน console.log ได้ text ก้อนเดียว ค้นยาก

💡 อย่า log ความลับ (password, token, เลขบัตร) ลง log เด็ดขาด — log รั่วได้ · pino มี redact ตัด field พวกนี้อัตโนมัติ

Observability ครบสามขา: log + metrics + traces ด้วย OpenTelemetry

ปี 2026 มาตรฐานอุตสาหกรรมคือ OpenTelemetry (OTel) — spec กลางที่ vendor-neutral สำหรับเก็บ log/metrics/traces ส่งไปยัง backend อะไรก็ได้ (Datadog, New Relic, Grafana, Jaeger, ...)

bash
npm install @opentelemetry/api @opentelemetry/auto-instrumentations-node \
            @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http
javascript
// otel.mjs — โหลดก่อนโค้ดอื่นทุกอย่าง (node --import ./otel.mjs server.mjs)
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";

const sdk = new NodeSDK({
  serviceName: "my-api",
  traceExporter: new OTLPTraceExporter({ url: process.env.OTEL_ENDPOINT }),
  instrumentations: [getNodeAutoInstrumentations()],   // auto-trace Express/HTTP/pg/Redis ให้
});
sdk.start();
bash
# รันแอปพร้อม OTel
node --import ./otel.mjs server.mjs

ได้อะไร: distributed trace (ตามคำขอข้าม service ได้ว่ามันใช้เวลาที่ไหนนาน), latency metrics, error rate — ครบ ไม่ต้องเขียนเอง · ลึกขึ้นที่ Observability


4. Security checklist — อย่าให้โดนแฮก

backend ที่เปิดสู่อินเทอร์เน็ตคือเป้า — เช็กลิสต์ขั้นต่ำ (ลึกขึ้นที่ Security + API Design บทที่ 4):

bash
npm install helmet express-rate-limit rate-limit-redis ioredis argon2
# helmet v8+ รองรับ ESM ในตัว (เวอร์ชันเก่ากว่านี้เป็น CJS-only)
javascript
import helmet from "helmet";       // ตั้ง security headers ให้อัตโนมัติ
import rateLimit from "express-rate-limit";
import { RedisStore } from "rate-limit-redis";
import Redis from "ioredis";

app.use(helmet());                 // ป้องกัน header-based attack หลายชนิด
app.use(express.json({ limit: "100kb" }));   // จำกัดขนาด body กัน payload ระเบิด memory

// ⭐ ⚠️ สำคัญมาก: express-rate-limit ค่า default เก็บ counter ใน RAM ของ process เดียว
// → ถ้ารันหลาย instance หลัง LB จะ count แยกกัน → ทะลุ limit จริงเป็นเท่าตัว instance
// → production ต้องใช้ external store (Redis) ที่แชร์ระหว่าง instance
const redis = new Redis(process.env.REDIS_URL);
app.use(rateLimit({
  windowMs: 60_000,
  max: 100,                        // 100 req/นาที/IP — รวมกันทุก instance
  store: new RedisStore({ sendCommand: (...args) => redis.call(...args) }),
}));

🚀 โซนขั้นสูง — ข้ามได้ถ้ายังใหม่กับ security: CRIME/BREACH กับ compression()

  • CRIME/BREACH คือชื่อการโจมตีแบบ "side-channel" (เดาความลับทางอ้อมจากพฤติกรรมของระบบ แทนที่จะแฮกตรง ๆ) — ในที่นี้คือเดา session token/CSRF token จากขนาดของ response ที่ถูกบีบอัด (gzip)
  • เกิดตอนไหน: ถ้าใช้ compression() middleware (gzip response อัตโนมัติ) กับ endpoint ที่มีทั้ง "ความลับ" (เช่น CSRF token, session token) และ "input จาก user" ปนอยู่ในหน้าตอบกลับเดียวกัน (เช่น endpoint ที่ echo token กลับมาในหน้า) — ผู้โจมตีลองยิง input ต่าง ๆ แล้วดูว่าขนาด response บีบอัดแล้วเล็กลงไหม จะไล่เดาตัวอักษรของ token ได้ทีละตัว
  • ทางแก้ (เลือกอย่างใดอย่างหนึ่ง):
    1. ปิด compression สำหรับ auth/CSRF endpoint
    2. ใช้ Cache-Control: no-transform + เปลี่ยน secret บ่อย ๆ
    3. เช็คให้แน่ใจว่า response นั้นไม่มีทั้ง secret + user input ปนกันในหน้าเดียว

🔐 Password hashing: เก็บ password ต้อง hash ด้วย Argon2id (ตัวเลือกอันดับ 1 ตาม OWASP ปี 2026 เพราะเป็น memory-hard — ต้าน GPU/ASIC crack ได้ดีกว่า) — npm install argon2 แล้ว await argon2.hash(pwd, { type: argon2.argon2id }) · bcrypt ยังปลอดภัยและใช้ได้อยู่ ถ้าโปรเจกต์เก่ามีอยู่แล้วไม่จำเป็นต้องรีบ migrate ด่วน · แต่อย่าใช้ SHA/MD5 เด็ดขาด (เร็วเกินไป crack ง่าย — คนละระดับกับ bcrypt)

เรื่องทำอะไรบทที่เกี่ยว
SQL injectionparameterized query ($1) เสมอบทที่ 9
Command injectionexecFile/spawn ไม่ใช่ exec กับ user inputบทที่ 10
Input validationvalidate ทุก input ด้วย Zodบทที่ 7, 9
Secretsenv เท่านั้น, ไม่ commit .env, ไม่ logบทที่ 7
Dependenciesnpm audit หา vuln, อัปเดต lockfile สม่ำเสมอบทที่ 1
HTTP headershelmetนี้
Rate limitingexpress-rate-limit + Redis store (หลาย instance)นี้
Password hashingArgon2id (argon2) แนะนำสุด · bcrypt ยังใช้ได้ · ห้าม SHA/MD5นี้
CRIME/BREACHระวัง compression() กับ response มีทั้ง secret + user inputนี้
ไม่รันเป็น rootUSER node ใน Dockerข้อ 1
bash
npm audit               # สแกน dependency หาช่องโหว่ที่รู้แล้ว
npm audit fix           # แก้ที่แก้ได้อัตโนมัติ

⚠️ Pitfall: npm install package มั่ว ๆ จาก npm โดยไม่ตรวจ — npm มี supply chain attack (package ปลอม/ถูกแฮก ฝัง malware) ตรวจชื่อให้ดี ระวัง typo — มี typosquatting (ตั้งชื่อ package สะกดคล้ายของดัง เช่น expres แทน express หวังคนพิมพ์ผิดแล้ว install) ดู Security บทที่ 6


5. Modern Node — ฟีเจอร์ที่ควรรู้ (2026)

Node สมัยใหม่ (Node 22 LTS เป็น default ของ production ปี 2026 · Node 20 LTS ยัง maintenance อยู่ · Node 24+ เป็น current) มีของในตัวที่เมื่อก่อนต้องลง package — ลด dependency ลงเยอะ:

javascript
// fetch built-in (v18+) — ไม่ต้องลง axios/node-fetch (บทที่ 5)
const res = await fetch(url);

// node:test + assert built-in (v20+) — ไม่ต้องลง Jest สำหรับงานพื้นฐาน (บทที่ 8)

// --watch (v18+) — restart อัตโนมัติ ไม่ต้องลง nodemon
//   node --watch server.mjs

// --env-file (v20.6+) — โหลด .env ไม่ต้องลง dotenv (บทที่ 7)
//   node --env-file=.env server.mjs

// glob built-in (v22+, stable ใน 22.x) — หาไฟล์ตาม pattern
// (ในเวอร์ชันก่อนหน้าเป็น experimental — เช็ก docs ของรุ่นที่ใช้)
import { glob } from "node:fs/promises";
for await (const file of glob("src/**/*.mjs")) console.log(file);

// Permission Model (ยังเป็น experimental ใน Node 22) — จำกัดสิทธิ์โปรแกรม
// Node 22+ ใช้ flag --permission (ของเก่า --experimental-permission ยังใช้ได้แต่ deprecated)
//   node --permission --allow-fs-read=./data server.mjs
// → โค้ดเขียนไฟล์/เปิด network ที่ไม่ได้อนุญาตจะ throw ทันที

มีแม้แต่ single executable application (SEA) — รวม Node runtime + โค้ดของเราเป็นไฟล์ .exe (Windows) / binary (Linux/macOS) ไฟล์เดียว แจกให้ user ที่ไม่มี Node ก็รันได้ทันที (ยังเป็น experimental — เหมาะกับ CLI tool ไม่ใช่ web server)

TypeScript กับ Node

โปรเจกต์จริงส่วนใหญ่เขียน Node ด้วย TypeScript (TypeScript track) — มี 3 ทางเลือก:

bash
# 1. tsx — รัน .ts ตรง ๆ ระหว่าง dev (เร็ว นิยมสุด)
npx tsx watch src/server.ts

# 2. native type stripping — Node รัน .ts ได้เองโดยลบ type ทิ้ง (ไม่ check type)
#    Node 22.6+: ต้องใส่ flag --experimental-strip-types
#    เวอร์ชันใหม่กว่านี้อาจเปิด default แล้ว — เช็ก release notes ของ Node ที่ใช้จริงก่อน
node --experimental-strip-types src/server.ts
node src/server.ts   # ถ้า Node เวอร์ชันที่ใช้เปิด strip-types เป็น default แล้ว ไม่ต้องใส่ flag

# 3. build เป็น .js ก่อน deploy (production)
npx tsc                  # แปลง src/*.ts → dist/*.js แล้วรัน node dist/server.js

⚠️ ข้อจำกัดของ strip-types: Node ลบ type ทิ้งเฉย ๆ ไม่ได้ "แปลง" ดังนั้นฟีเจอร์ TypeScript ที่ต้องการ runtime transform (โค้ดที่ต้องถูกแปลงเป็นโค้ดจริงที่รันได้ ไม่ใช่แค่ลบ type annotation ทิ้ง) จะ ใช้ไม่ได้enum, namespace, decorator พร้อม metadata (ที่ NestJS ใช้) · ถ้าใช้ NestJS ตอนนี้ยังต้อง build ด้วย tsc/swc อยู่ · และ strip-types ไม่ check type — ต้องรัน tsc --noEmit แยกเพื่อ type-check

💡 ข้อมูล ณ 2026: Node กำลังเดินหน้าให้รัน TypeScript ได้ในตัว (strip type) — แต่ production ตอนนี้ยังนิยม build ด้วย tsc/esbuild/swc ก่อน deploy เพื่อความเสถียร · dev ใช้ tsx

Deno และ Bun — runtime ทางเลือก

🚀 โซนขั้นสูง — ข้ามได้ ส่วนนี้แค่ให้ "รู้จักไว้" ว่ามี runtime อื่นนอกจาก Node — มือใหม่โฟกัส Node ให้แน่นก่อน ไม่ต้องรีบลอง Deno/Bun

Node ไม่ใช่ runtime เดียวของ JS แล้ว — รู้จักไว้:

Runtimeจุดเด่นสถานะ (2026)
Node.jsecosystem ใหญ่สุด, เสถียร, มาตรฐานอุตสาหกรรมครองตลาด ใช้ใน production ทั่วโลก
DenoTypeScript ในตัว, security-first (permission), std lib ดีนิยมในงานใหม่/script, เข้ากับ npm ได้แล้ว
Bunเร็วมาก, มี bundler/test/package manager ในตัวโตเร็ว, ใช้ใน production มากขึ้น

💡 เริ่มต้น/หางาน → Node (ความต้องการสูงสุด, สิ่งที่เรียนมาทั้งเล่มใช้ได้) · Bun/Deno น่าจับตาและลองได้ แต่ Node คือพื้นฐานที่ทุกอย่างอ้างอิง — เข้าใจ Node แล้วย้ายไป Bun/Deno ง่าย เพราะ API ส่วนใหญ่เลียนแบบ Node


6. 🚀 Roadmap — ก้าวต่อไป

คุณเข้าใจ Node ตั้งแต่ runtime ถึง production แล้ว ต่อไป:

  • ➡️ NestJS — เป้าหมายหลักของเล่มนี้ ตอนนี้คุณพร้อมแล้ว: รู้ว่า decorator/DI/middleware/pipeline ที่ Nest ใช้ — ข้างใต้คือ http + Express + EventEmitter + การจัดการ async/error ที่คุณเพิ่งเรียน · ประโยค "ปัญหาของ Express เปล่า ๆ" ใน Nest บทที่ 0 จะชัดเจนทันที
  • React + Next.js — fullstack: Next.js รัน React บน Node (SSR/Server Components/Server Actions) — ความรู้ Node ทำให้เข้าใจว่าฝั่ง server ทำงานยังไง
  • API Design — ออกแบบ REST/GraphQL/gRPC ให้ดี (ต่อยอด REST API บทที่ 9)
  • DatabaseSQL ลึก, indexing, transaction (ต่อยอด pg บทที่ 9)
  • Microservices + Observability — เมื่อระบบโตเป็นหลายบริการ

แหล่งอ้างอิงทางการ

  • Node.js Docs — เอกสาร API ทางการ (อ่านได้จริง ครบ)
  • Node.js Learn — บทเรียนทางการ
  • Express · pino · Zod
  • 12 Factor App — หลักการทำแอปที่ deploy ดี

🛠️ Checkpoint 11 — ปิดเล่ม

  1. เขียน Dockerfile multi-stage สำหรับ API บทที่ 9 (slim, non-root, --omit=dev, .dockerignore) แล้ว docker build + docker run ให้ใช้งานได้
  2. เพิ่ม /healthz (liveness) + /readyz (เช็ก DB จริง) แล้วลองหยุด DB ดูว่า /readyz ตอบ 503
  3. เปลี่ยน logging ทั้งแอปจาก console.log เป็น pino (มี level + context) — ดู output ที่เป็น JSON
  4. รัน npm audit กับโปรเจกต์ตัวเอง — มีช่องโหว่ไหม ลองอ่านว่าแต่ละอันคืออะไร
  5. เพิ่ม helmet + express-rate-limit + จำกัดขนาด body แล้วยิง request เกิน limit ดูว่าโดนบล็อก
  6. ลองรัน TypeScript ด้วย tsx และ node --experimental-strip-types เทียบกัน
  7. ปิดเล่ม: เปิด NestJS บทที่ 0 อ่านอีกครั้ง — สังเกตว่าตอนนี้เข้าใจทุกประโยคแล้ว แล้วเริ่ม track NestJS ได้เลย 🎉

สรุปบทที่ 11 + ปิดเล่ม

  • Docker: multi-stage + node:22-slim + USER node + npm ci --omit=dev + .dockerignore + HEALTHCHECK + CMD ["node", ...] (ไม่ใช่ npm)
  • Process manager: PM2 บน VM (-i max) · ใน container/K8s รัน node ตรง ๆ + terminationGracePeriodSeconds + preStop sleep + startup/liveness/readiness probe + PodDisruptionBudget
  • Health check: liveness (/healthz) แยกจาก readiness (/readyz เช็ก DB มี timeout + warm-up gate) · structured logging ด้วย pino + pino-http (JSON) · OpenTelemetry สำหรับ trace/metrics
  • Security: parameterized query, validate input, helmet v8+, express-rate-limit + Redis store (สำคัญสำหรับหลาย instance), Argon2id สำหรับ password, ระวัง CRIME/BREACH กับ compression(), จำกัด body, secrets ใน env, npm audit, ไม่รัน root
  • Modern Node: fetch/node:test/--watch/--env-file/glob built-in · --permission (experimental sandbox) · TypeScript ผ่าน tsx/strip-types (มีข้อจำกัด)/tsc · Deno/Bun = ทางเลือก แต่ Node คือพื้นฐาน
  • ก้าวต่อไป → NestJS (คุณพร้อมแล้ว) · Next.js · API Design · Database · Microservices

🎉 จบ track Node.js — คุณเดินทางจาก "JavaScript คือภาษาในเบราว์เซอร์" มาถึง "backend ที่ deploy ขึ้น production ได้จริง" และที่สำคัญที่สุด: คุณ เข้าใจว่าใต้ framework มีอะไร — ซึ่งคือสิ่งที่ทำให้คุณใช้ NestJS/Next.js แบบรู้จริง ไม่ใช่แบบงม ๆ

← กลับสารบัญ Node.js | ➡️ ไปต่อที่ NestJS