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

process & env

process เป็น global object ที่ให้โปรแกรมของคุณเข้าถึง Node.js process ที่กำลังทำงานอยู่ มันใช้งานได้ทุกที่ — ไม่ต้อง import

property/method ที่ใช้บ่อยที่สุด:

Property / Methodสิ่งที่ได้รับ
process.argvarray ของ command-line arguments
process.envobject ของ environment variables
process.cwd()string ของ current working directory
process.exit(code)สิ้นสุด process ด้วย exit code
process.platformstring platform ของ OS ('linux', 'darwin', 'win32')
process.versionstring version ของ Node.js (เช่น 'v20.11.0')

process.argv เป็น array เสมอ สององค์ประกอบแรกคงที่:

  • argv[0] — path ไปยัง binary node
  • argv[1] — path ไปยัง script ที่กำลังรัน
  • argv[2] เป็นต้นไป — argument จริงของคุณ
Node.js

Needs the Node.js runtime — open in StackBlitz to run.

process.env เป็น plain object ที่ key และ value เป็น string ทั้งหมด มันถูกเติมข้อมูลจาก shell environment ตอน process เริ่มทำงาน

Node.js

Needs the Node.js runtime — open in StackBlitz to run.

ทุก key และ value ใน process.env เป็น string แม้ shell จะตั้งค่าให้ดูเหมือนตัวเลขหรือ boolean ก็ตาม PORT=3000 ใน environment จะให้ process.env.PORT === '3000' ไม่ใช่ 3000 relational operator (<, >) จะ coerce string operand เป็นตัวเลขให้อัตโนมัติ แต่ === และ + ไม่ทำ — ความไม่ตรงกันนี้เป็นหนึ่งในบั๊ก process.env ที่พบบ่อยที่สุด:

Node.js
Output (verified on Node 24)
typeof PORT: string
PORT === 3000: false
PORT === "3000": true
PORT + 1: 30001
Number(PORT) + 1: 3001
PORT > 1024: true
unset PORT > 1024: false
Number(unset PORT): NaN

ตั้งแต่ Node 22 runtime โหลดไฟล์ env ได้เอง — จาก CLI ด้วย --env-file หรือจาก code ด้วย process.loadEnvFile():

Terminal window
node --env-file=.env src/app.js
import { writeFileSync, unlinkSync } from 'node:fs';
import { loadEnvFile } from 'node:process';
writeFileSync('.env', 'PORT=4000\n'); // demo fixture
loadEnvFile(); // defaults to ./.env
console.log(process.env.PORT);
unlinkSync('.env');
Node.js

Needs the Node.js runtime — open in StackBlitz to run.

process.exit(code) สิ้นสุด process ทันที ตามธรรมเนียม:

  • exit code 0 แปลว่าสำเร็จ
  • exit code ที่ไม่ใช่ 0 (โดยทั่วไปคือ 1) แปลว่าล้มเหลว
// @expect-throw
// Example: exit with error if a required env var is missing
if (!process.env.DATABASE_URL) {
console.error('ERROR: DATABASE_URL is required');
process.exit(1);
}

การเรียก process.exit() คือการหยุดแบบทันที — มันข้าม pending async callback และ finally block ใด ๆ ที่ยังอยู่บน call stack แนะนำให้ตั้งค่า process.exitCode แทน (เช่น process.exitCode = 1) แล้วปล่อยให้ process จบตามธรรมชาติ — นั่นให้โอกาส pending I/O, finally block และ graceful shutdown (เช่น server.close()) ได้ทำงาน

Node.js
Output (verified on Node 24)
exitCode set to 1 (tentative)
setImmediate callback ran -- exitCode does NOT skip pending callbacks
Node.js
Output (verified on Node 24)
about to hard-exit with process.exit(1)

setImmediate ตัวเดียวกัน แต่ผลลัพธ์ต่างกันสองแบบ: process.exitCode ปล่อยให้มันทำงานก่อน process จะจบ; process.exit() ตัดมันทิ้งกลางคัน

Orchestrator (Docker, Kubernetes, systemd, Ctrl+C ใน terminal) หยุด process ด้วยการส่ง signal ไม่ใช่การฆ่าทิ้งตรง ๆ: SIGTERM คือการขอแบบสุภาพ SIGINT คือสิ่งที่ Ctrl+C ส่งมา Node ให้คุณดักจับทั้งสองด้วย process.on() เพื่อทำงานที่ค้างอยู่ให้เสร็จ — หยุดรับการเชื่อมต่อใหม่ ปล่อยให้ request ที่มีอยู่เสร็จ ปิดการเชื่อมต่อฐานข้อมูล — ก่อนที่จะจบจริง ๆ:

Node.js
Output (verified on Node 24)
server started (simulated)
this line runs synchronously -- the signal handler fires after it
SIGTERM received -- closing server gracefully
cleanup done, exiting 0

process.nextTick(fn) เข้าคิว fn ให้ทำงาน ก่อน ที่ event loop จะไปยัง phase ถัดไป — ก่อนแม้แต่ microtask อื่น (promise callback) ที่เข้าคิวไว้ก่อนหน้า มันถูกครอบคลุมอย่างละเอียด พร้อมลำดับเต็มเทียบกับ Promise, setImmediate และ setTimeout ใน Microtasks & Macrotasks สรุปสั้น ๆ ที่เกี่ยวข้องตรงนี้: process คือที่ที่ nextTick อาศัยอยู่ แต่จริง ๆ แล้วมันเป็น primitive ของ event loop ไม่ใช่ feature เฉพาะของ process — มันอยู่บน process ด้วยเหตุผลทางประวัติศาสตร์ (มันมีมาก่อน queueMicrotask)

ตามที่ครอบคลุมไปแล้วในภาพรวมของโมดูลนี้ process ไม่ต้อง import เลย — มันเป็น global จริง เหมือน Buffer module ทุกตัวที่ใช้ในหน้านี้ (node:util, node:fs, node:child_process) สามารถ import ได้ทั้งมีและไม่มี prefix node: ถ้า มันเป็น module รุ่นเก่า; built-in ใหม่เอี่ยมอย่าง node:test และ node:sqlite resolve ได้เฉพาะที่มี prefix เท่านั้น เมื่อคุณเห็น require('util') ใน code เก่า มันเทียบเท่ากับ require('node:util') — prefix เป็น convention เพื่อความชัดเจน ไม่ใช่การเปลี่ยนพฤติกรรม สำหรับอะไรก็ตามที่มีมาก่อนมัน

node:util มาพร้อม utility เล็ก ๆ สองตัวที่ลดความจำเป็นในการใช้ npm package ยอดนิยมสำหรับ CLI tool ง่าย ๆ parseArgs (stable ตั้งแต่ 20.x) แทน yargs/minimist สำหรับการ parse flag ตรงไปตรงมา:

Node.js
Output (verified on Node 24)
[Object: null prototype] { name: 'Ada', verbose: true, count: '3' }

styleText (stable ตั้งแต่ 22.x) เพิ่ม ANSI color/style code ให้ string — แทน chalk/picocolors สำหรับกรณีพื้นฐาน มันตรวจสอบอัตโนมัติว่า stream ปลายทางรองรับสีจริงหรือไม่ และ คืนค่า string เปล่าไม่เปลี่ยนแปลงถ้าไม่รองรับ (piped output, log file, CI runner ส่วนใหญ่):

Node.js
Output (verified on Node 24)
"OK"
length with codes: 2 vs plain length: 2
flowchart TB
  Env["process.env.PORT"] -->|"always"| Str["a string, never a number"]
  Str -->|"=== or arithmetic"| Trap["silent bug: false / string concat"]
  Str -->|"Number() first"| Safe["correct comparison/arithmetic"]
  Exit1["process.exitCode = n"] -->|"lets pending callbacks + finally run"| Natural["process exits naturally"]
  Exit2["process.exit(n)"] -->|"cuts off immediately"| Hard["pending callbacks + finally skipped"]
  Sig["SIGTERM / SIGINT"] -->|"no listener"| Immediate["process dies immediately, mid-request"]
  Sig -->|"process.on(signal, ...)"| Graceful["finish in-flight work, then exit(0)"]
process.env เป็น string เสมอ; exitCode ปล่อยให้งานที่ค้างทำเสร็จก่อนในขณะที่ exit() ตัดมันทิ้ง; signal ที่ไม่มีคนจัดการฆ่า process ทันที ส่วนที่จัดการแล้วจบแบบ graceful
ทางเลือกข้อดีต้นทุน
อ่าน config ผ่าน process.envcode เดียวกัน deploy ได้ทุก environment (12-Factor); ไม่ต้อง rebuild สำหรับ config ใหม่ทุกอย่างเป็น string — ค่าตัวเลข/boolean ทุกตัวต้องแปลงและ validate อย่างชัดเจน
process.exitCode = npending I/O, timer และ finally block ได้ทำงาน; ดีฟอลต์ที่ปลอดภัยกว่าต้องการให้ event loop drain จริง ๆ — handle ที่ค้างอยู่จะทำให้ process ไม่จบ
process.exit(n)รับประกันการสิ้นสุดทันที มีประโยชน์เมื่อมีอะไรค้างจริง ๆข้าม finally block และ pending callback แบบไม่มีเงื่อนไข — รวมถึงตัวที่ flush log หรือปิดการเชื่อมต่อ
จัดการ SIGTERM เองrequest ที่ค้างอยู่ได้เสร็จ; การเชื่อมต่อปิดอย่างสะอาดเพิ่ม code เส้นทาง shutdown ที่พลาดง่าย (ต้องเรียก exit() จริง ๆ ในที่สุด ไม่งั้น grace period ของ orchestrator หมดแล้วมันจะ SIGKILL คุณอยู่ดี)
  • “process.env.PORT > 1024 ไม่ปลอดภัยเพราะ PORT เป็น string” — ความจริง: relational operator (</>) coerce string operand เป็นตัวเลขให้อัตโนมัติ; กับดักจริง ๆ คือ === (ไม่ coerce เลย) และ + (concatenate string แทนที่จะบวก)
  • “process.exit() กับการตั้ง process.exitCode ทำสิ่งเดียวกัน แค่ syntax ต่างกัน” — ความจริง: exitCode ปล่อยให้ event loop drain ตามธรรมชาติ — pending callback และ finally block ได้ทำงาน; exit() สิ้นสุดทันที ข้ามทั้งสองอย่าง
  • “ถ้าไม่มี signal handler Kubernetes จะรอให้ process ของฉันทำ request ปัจจุบันเสร็จ” — ความจริง: ถ้าไม่มี listener SIGTERM พฤติกรรมดีฟอลต์จะสิ้นสุด process ทันที; มีแค่ handler ที่ชัดเจนเท่านั้นที่ซื้อเวลาทำงานที่ค้างอยู่ให้เสร็จ และซื้อได้แค่ในขอบเขต grace period ของ orchestrator
  • “styleText เพิ่ม color code ให้ string ของฉันเสมอ” — ความจริง: มันตรวจสอบก่อนว่า stream ปลายทางรองรับสีหรือไม่ และคืนค่า string เปล่าแบบเงียบ ๆ ถ้าไม่รองรับ (piped output, log file, CI ส่วนใหญ่)
  • code เปรียบเทียบ process.env.SOMETHING ด้วย === กับตัวเลข หรือส่งมันเข้าคำนวณโดยตรงด้วย + โดยไม่มีการแปลง Number(...)/parseInt(...) อย่างชัดเจนก่อนหรือเปล่า?
  • script เรียก process.exit() ทันทีหลัง console.log() หรือการเขียน fs.writeFile()/database ที่อาจยังค้างอยู่หรือเปล่า? บาง platform buffered stdout อาจถูกตัดทอน และ async write ที่ค้างอยู่จะถูกทิ้งไปเลย
  • server ที่รันระยะยาวลงทะเบียน SIGTERM/SIGINT handler ไว้เลยหรือเปล่า? การไม่มีมันแปลว่าทุกครั้งที่ deploy จะฆ่า request ที่ค้างอยู่แทนที่จะทำให้เสร็จ
  • logic การเคลียร์อยู่ใน finally block ที่รันเฉพาะเส้นทางออกตามธรรมชาติ ในขณะที่ branch ก่อนหน้าเรียก process.exit() ตรง ๆ — ข้ามการเคลียร์เดียวกันนั้นไปแบบเงียบ ๆ หรือเปล่า?

🐛 หา bug ในโค้ดนี้

import http from 'node:http';
var server = http.createServer(function(req, res) {
res.end('ok');
});
server.listen(process.env.PORT || 3000, function() {
console.log('listening on port', process.env.PORT || 3000);
});
เฉลย

process.env.PORT เป็น string (หรือ undefined) server.listen() บังเอิญรับ string port number ได้ ดังนั้น call นี้เฉพาะเจาะจงไม่ crash — แต่ทันทีที่ค่านี้ถูกใช้ที่ไหนก็ตามด้วย ===, การคำนวณ หรือการตรวจ type (typeof port === 'number', validation schema, health-check ที่เทียบกับช่วงตัวเลข) type ที่เป็น string จะทำให้เกิดบั๊กแบบเงียบ วิธีแก้คือแปลงครั้งเดียวใกล้ด้านบน: var port = Number(process.env.PORT) || 3000; เพื่อให้การใช้ port ทุกครั้งในภายหลังเป็นตัวเลขจริง

process.argv[0] มีอะไรอยู่เมื่อคุณรัน node script.js?
ค่าที่เก็บใน process.env เป็น type อะไร?
exit code ใดที่ตามธรรมเนียมแปลว่าสำเร็จ?
โค้ดนี้พิมพ์อะไร? process.env.PORT = "3000"; console.log(process.env.PORT === 3000); console.log(process.env.PORT > 1024);
function หนึ่งเรียก process.exit(1) ทันทีหลังจากเริ่ม setImmediate(() => cleanup()) cleanup() จะทำงานไหม?
เกิดอะไรขึ้นกับ Node.js server process ที่ได้รับ SIGTERM โดยไม่มี signal handler ลงทะเบียนไว้?