Node.js 完整教學指南
專業的 Node.js 繁體中文教學及練習範例網站,提供Node.js安裝教學、 Node.js用途介紹、 Node.js架站教學、 Node.js後端教學、 Vscode Node.js教學、 Node.js express 教學,助你由入門到專家掌握Node.js編程語言!
由「香港編程學院」呈獻,從零開始學習伺服器端 JavaScript 開發,建構高效能、可擴展的後端應用程式。
入門介紹 | 什麼是Node.js?
Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行環境,由 Ryan Dahl 於 2009 年創建。它讓開發者能夠使用 JavaScript 語言在伺服器端撰寫程式,打破了 JavaScript 只能在瀏覽器中執行的限制。
高效能
非阻塞 I/O 架構,單執行緒事件迴圈,能輕鬆每秒處理數萬請求。
全端開發
前後端統一使用 JavaScript,大幅降低學習成本與溝通障礙。
豐富生態
npm (Node Package Manager) 擁有超過 200 萬個開源套件。
靈活部署
完美支援雲端服務、Docker 容器化以及微服務架構與邊緣運算。
事件迴圈 (Event Loop)
Node.js 的核心機制。它允許 Node.js 執行非阻塞 I/O 操作 — 儘管 JavaScript 只有單一執行緒,藉由將操作轉移給系統核心來實現高併發。
第一個 Node.js 程式
// 最簡單的 Node.js 程式
console.log('Hello, Node.js!');
// 查看 Node.js 環境變數與版本
console.log(`Node.js 版本: ${process.version}`);
console.log(`平台: ${process.platform}`);
console.log(`架構: ${process.arch}`);
安裝設定
在開始撰寫程式之前,我們需要在電腦上準備好 Node.js 執行環境。我們強烈建議安裝 LTS (長期支援) 版本。
下載 Node.js
前往 nodejs.org 下載對應您作業系統的 LTS 安裝檔。
執行安裝
執行下載的安裝程式,並按照指示完成安裝。安裝過程會自動將 node 和 npm 加入系統路徑。
驗證安裝
開啟終端機 (Terminal) 或命令提示字元,輸入下方指令驗證。
# 驗證 Node.js 安裝
node -v
# v20.11.0
# 驗證 npm 安裝
npm -v
# 10.2.4
# 執行 Node.js 互動式 REPL
node
使用 Node Version Manager (nvm) 可以讓您在同一台電腦上輕鬆切換不同的 Node.js 版本,避免專案間的版本衝突。
# 使用 nvm 管理多版本 (macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# 安裝特定版本
nvm install 20
nvm use 20
nvm list
建立您的專案
// hello.js - 第一個 Node.js 程式
const name = 'World';
console.log(`Hello, ${name}!`);
// 執行命令
// node hello.js
模組系統
Node.js 的模組系統讓您可以將程式碼拆分成多個檔案,提高維護性與重用性。目前 Node.js 支援兩種模組系統:傳統的 CommonJS 以及現代瀏覽器也支援的 ES Modules (ESM)。
CommonJS 是 Node.js 預設的模組規範,使用 require() 和 module.exports。
// 📁 math.js - 建立模組
const PI = 3.14159;
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
function circleArea(r) {
return PI * r * r;
}
// 匯出模組
module.exports = {
add,
multiply,
circleArea
};
// 📁 app.js - 使用模組
const math = require('./math');
console.log(math.add(5, 3)); // 8
console.log(math.multiply(4, 6)); // 24
console.log(math.circleArea(5)); // 78.53975
// 解構賦值匯入
const { add, circleArea } = require('./math');
console.log(add(10, 20)); // 30
要使用 ES Modules,您需要在 package.json 中設定 "type": "module",或使用 .mjs 副檔名。
// 📁 math.mjs - ES Module 匯出
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// 預設匯出
export default function circleArea(r) {
return PI * r * r;
}
// 📁 app.mjs - ES Module 匯入
import circleArea, { add, multiply, PI } from './math.mjs';
console.log(add(5, 3)); // 8
console.log(multiply(4, 6)); // 24
console.log(circleArea(5)); // 78.53975
console.log(PI); // 3.14159
內建模組 (Built-in Modules)
Node.js 提供了豐富的核心模組,無需額外安裝即可直接使用:
| 模組名稱 | 功能說明 |
|---|---|
fs | 檔案系統操作 (讀寫、建立、刪除) |
http/https | 建立 HTTP/HTTPS 伺服器與客戶端請求 |
path | 跨平台的檔案路徑處理與解析 |
os | 獲取作業系統資訊 (CPU、記憶體、網路介面) |
events | 事件發射與監聽系統 (Event Emitter) |
stream | 高效處理大型資料的串流技術 |
crypto | 加密、解密與雜湊功能 |
child_process | 建立與執行子程序 |
url | URL 字串解析與格式化 |
HTTP 伺服器
Node.js 最常見的用途是建構 Web 伺服器。透過內建的 http 模組,您可以精細地控制 HTTP 請求與回應。
基本伺服器建構
// 📁 server.js - 基本 HTTP 伺服器
const http = require('http');
const server = http.createServer((req, res) => {
const { method, url } = req;
console.log(`${method} ${url}`);
// 設定回應標頭
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
'X-Powered-By': 'Node.js'
});
res.end(`
Hello from Node.js!
方法: ${method}
路徑: ${url}
`);
});
server.listen(3000, '127.0.0.1', () => {
console.log('✅ 伺服器啟動於 http://127.0.0.1:3000');
});
手動實作路由 (Routing)
在沒有使用框架的情況下,我們可以使用 req.url 與 req.method 來區分不同的請求並回傳對應內容。
// 📁 router.js - 路由處理
const http = require('http');
const url = require('url');
const routes = {
'GET /': (res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: '首頁', status: 'ok' }));
},
'GET /users': (res) => {
const users = [
{ id: 1, name: '陳小明', email: '[email protected]' },
{ id: 2, name: '王小美', email: '[email protected]' }
];
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(users));
},
'GET /about': (res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('關於我們
Node.js 教學網站
');
}
};
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const routeKey = `${req.method} ${parsedUrl.pathname}`;
if (routes[routeKey]) {
routes[routeKey](res, parsedUrl.query);
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: '找不到頁面', code: 404 }));
}
});
server.listen(3000);
console.log('伺服器已啟動');
原生的 HTTP 模組雖然強大,但處理 URL 參數、POST 請求本體解析、Cookie 與 Session 等功能都需從零刻起。因此實務上通常會使用 Express 等框架。
檔案系統 (fs)
fs 模組負責所有的檔案操作。Node.js 提供了三種風格的 API:傳統回調 (Callback)、同步 (Sync) 與 Promise。
// 📁 async-fs.js - 非同步檔案操作 (Callback)
const fs = require('fs');
const path = require('path');
// ✅ 讀取檔案
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
console.error('❌ 讀取失敗:', err.message);
return;
}
console.log('📄 檔案內容:', data);
});
// ✅ 寫入檔案
const content = '這是寫入的內容\n日期: ' + new Date().toLocaleString('zh-TW');
fs.writeFile('output.txt', content, 'utf8', (err) => {
if (err) {
console.error('❌ 寫入失敗:', err.message);
return;
}
console.log('✅ 檔案寫入成功');
});
// ✅ 附加內容到檔案
fs.appendFile('log.txt', `[${new Date().toISOString()}] 應用程式啟動\n`, (err) => {
if (err) throw err;
console.log('✅ 日誌已更新');
});
// ✅ 刪除檔案
fs.unlink('temp.txt', (err) => {
if (err && err.code !== 'ENOENT') throw err;
console.log('✅ 暫存檔案已刪除');
});
// 📁 fs-promises.js - 使用 Promise API (推薦寫法)
const fs = require('fs').promises;
const path = require('path');
async function fileOperations() {
try {
// 讀取檔案
const data = await fs.readFile('data.txt', 'utf8');
console.log('📄 內容:', data);
// 寫入檔案
await fs.writeFile('result.txt', '處理結果: ' + data.length + ' 字元', 'utf8');
console.log('✅ 已寫入結果');
// 建立目錄
await fs.mkdir('uploads', { recursive: true });
console.log('📁 目錄已建立');
// 讀取目錄
const files = await fs.readdir('./');
console.log('📂 目錄檔案:', files);
// 取得檔案資訊
const stats = await fs.stat('data.txt');
console.log('📊 檔案大小:', stats.size, 'bytes');
console.log('📅 修改時間:', stats.mtime);
} catch (err) {
console.error('❌ 錯誤:', err.message);
}
}
fileOperations();
當處理的檔案超過記憶體負載 (如 GB 級別的影片或日誌檔) 時,必須使用串流 (Stream)。
// 📁 stream.js - 串流處理大型檔案
const fs = require('fs');
const { Transform } = require('stream');
// 建立轉換串流 (將文字轉大寫)
const upperCaseTransform = new Transform({
transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
}
});
// 大型檔案複製 (記憶體友善)
const readStream = fs.createReadStream('large-input.txt', {
encoding: 'utf8',
highWaterMark: 64 * 1024 // 64KB 緩衝區
});
const writeStream = fs.createWriteStream('large-output.txt');
// 將讀取流導向轉換流,再導向寫入流 (Pipeline)
readStream
.pipe(upperCaseTransform)
.pipe(writeStream);
writeStream.on('finish', () => {
console.log('✅ 大型檔案處理完成');
});
readStream.on('error', (err) => {
console.error('❌ 讀取錯誤:', err);
});
NPM 套件管理
NPM (Node Package Manager) 隨 Node.js 一起安裝,是管理第三方套件與專案依賴的核心工具。每個專案的核心都是 package.json 檔案。
常用 NPM 指令
# 初始化新專案 (產生 package.json)
npm init
npm init -y # 使用預設值跳過問答
# 安裝套件
npm install express # 安裝到 dependencies (生產環境需要)
npm install -D nodemon # 安裝到 devDependencies (僅開發需要)
npm install -g pm2 # 全域安裝 (通常用於命令列工具)
# 更新套件
npm update # 更新所有套件
npm update express # 更新特定套件
# 移除套件
npm uninstall express
# 查看過期套件
npm outdated
# 執行腳本 (對應 package.json 內的 scripts)
npm start
npm run dev
npm test
package.json 結構解析
{
"name": "my-node-app",
"version": "1.0.0",
"description": "我的 Node.js 應用程式",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "jest",
"build": "npm run lint && npm run test"
},
"keywords": ["nodejs", "express", "api"],
"author": "陳小明 ",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"mongoose": "^8.0.0",
"jsonwebtoken": "^9.0.0",
"bcryptjs": "^2.4.3",
"dotenv": "^16.3.1"
},
"devDependencies": {
"nodemon": "^3.0.0",
"jest": "^29.0.0",
"eslint": "^8.0.0"
}
}
熱門必學套件
| 套件名稱 | 分類 | 說明 |
|---|---|---|
express | Web 框架 | 最流行的 Node.js Web 路由與中介軟體框架 |
mongoose | 資料庫 | MongoDB 的物件模型 (ODM) 工具 |
axios | HTTP 客戶端 | 強大的 HTTP 請求庫,支援 Promise |
jsonwebtoken | 認證 | JWT 令牌生成與驗證,用於無狀態登入 |
bcryptjs | 安全 | 密碼雜湊加密,防止明碼儲存 |
dotenv | 設定 | 從 .env 檔案載入環境變數 |
nodemon | 開發工具 | 監聽檔案變更並自動重啟伺服器 |
socket.io | 即時通訊 | 強大且穩定的 WebSocket 即時通訊解決方案 |
Express 框架
Express 是最受歡迎的 Node.js 輕量級 Web 框架,提供了優雅的路由定義方式及強大的中介軟體 (Middleware) 架構。
基本設定與路由
// 📁 app.js - Express 基本設定
const express = require('express');
const app = express();
// 中介軟體 (Middleware)
app.use(express.json()); // 解析 JSON
app.use(express.urlencoded({ extended: true })); // 解析表單
app.use(express.static('public')); // 靜態檔案伺服
// 路由宣告
app.get('/', (req, res) => {
res.json({ message: '歡迎使用 Node.js API!', version: '1.0.0' });
});
// 擷取網址參數 (Params) 與查詢字串 (Query)
app.get('/users/:id', (req, res) => {
const { id } = req.params; // 網址如 /users/123 -> id = 123
const { fields } = req.query; // 網址如 ?fields=name -> fields = name
res.json({ userId: id, fields });
});
// 404 處理 (放置在所有路由之後)
app.use((req, res) => {
res.status(404).json({ error: '找不到路由', path: req.path });
});
// 全域錯誤處理中介軟體
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: '伺服器內部錯誤' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`🚀 Express 伺服器啟動於 port ${PORT}`);
});
路由模組化 (Express Router)
當專案變大時,我們會將路由拆分到不同的檔案中。
// 📁 routes/users.js - 使用者路由模組
const express = require('express');
const router = express.Router();
// 模擬資料庫
let users = [
{ id: 1, name: '陳小明', email: '[email protected]', role: 'admin' },
{ id: 2, name: '王小美', email: '[email protected]', role: 'user' },
];
// GET /users - 取得所有使用者
router.get('/', (req, res) => {
const { role, page = 1, limit = 10 } = req.query;
let result = role ? users.filter(u => u.role === role) : users;
const start = (page - 1) * limit;
result = result.slice(start, start + parseInt(limit));
res.json({ data: result, total: users.length, page: parseInt(page) });
});
// POST /users - 新增使用者
router.post('/', (req, res) => {
const { name, email, role = 'user' } = req.body;
if (!name || !email) return res.status(400).json({ error: '姓名和電子郵件為必填' });
const newUser = { id: users.length + 1, name, email, role };
users.push(newUser);
res.status(201).json(newUser);
});
module.exports = router;
自訂中介軟體 (Middleware)
// 📁 middleware/auth.js - 認證中介軟體
const jwt = require('jsonwebtoken');
// 請求記錄中介軟體
function requestLogger(req, res, next) {
const start = Date.now();
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
res.on('finish', () => {
const duration = Date.now() - start;
console.log(` → ${res.statusCode} (${duration}ms)`);
});
next(); // 必須呼叫 next() 繼續執行
}
// JWT 驗證中介軟體
function authenticate(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: '需要提供認證令牌' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'secret');
req.user = decoded;
next();
} catch (err) {
res.status(403).json({ error: '無效的認證令牌' });
}
}
// 使用中介軟體
app.use(requestLogger);
app.use('/api/protected', authenticate);
module.exports = { requestLogger, authenticate };
app.use() 的順序就是執行順序,全域中介軟體要放在路由之前,錯誤處理中介軟體要放在最後。非同步程式設計
Node.js 的核心優勢在於非同步(Async)架構。理解 Callback、Promise 到 async/await 的演進,是掌握 Node.js 的關鍵。
const fs = require('fs');
// ⚠️ 回調地獄示範 - 難以閱讀和維護
fs.readFile('user.json', 'utf8', (err, userData) => {
if (err) return console.error('讀取使用者失敗:', err);
const user = JSON.parse(userData);
fs.readFile(`orders/${user.id}.json`, 'utf8', (err, orderData) => {
if (err) return console.error('讀取訂單失敗:', err);
const orders = JSON.parse(orderData);
fs.readFile(`products/${orders[0].productId}.json`, 'utf8', (err, productData) => {
if (err) return console.error('讀取商品失敗:', err);
const product = JSON.parse(productData);
// 😵 縮排越來越深...
console.log(`${user.name} 購買了 ${product.name}`);
});
});
});
const fs = require('fs').promises;
// ✅ Promise 鏈 - 更易讀
fs.readFile('user.json', 'utf8')
.then(data => JSON.parse(data))
.then(user => {
console.log('使用者:', user.name);
return fs.readFile(`orders/${user.id}.json`, 'utf8');
})
.then(data => JSON.parse(data))
.then(orders => {
console.log('訂單數量:', orders.length);
// 同時發出多個請求 (並行執行)
return Promise.all(orders.map(o =>
fs.readFile(`product-${o.productId}.json`, 'utf8')
.then(data => JSON.parse(data))
));
})
.then(products => console.log('商品清單:', products))
.catch(err => console.error('發生錯誤:', err.message))
.finally(() => console.log('操作結束'));
const fs = require('fs').promises;
// 🚀 Async/Await - 最直觀的寫法
async function fetchUserAndOrders(userId) {
try {
// 依序執行
const userData = await fs.readFile(`user-${userId}.json`, 'utf8');
const user = JSON.parse(userData);
console.log('使用者:', user.name);
// 並行執行 (效率更高)
const [ordersData, profileData] = await Promise.all([
fs.readFile(`orders-${userId}.json`, 'utf8'),
fs.readFile(`profile-${userId}.json`, 'utf8')
]);
return {
user,
orders: JSON.parse(ordersData),
profile: JSON.parse(profileData)
};
} catch (err) {
if (err.code === 'ENOENT') {
console.error('❌ 找不到檔案:', err.path);
} else {
console.error('❌ 未知錯誤:', err.message);
}
throw err;
}
}
// 呼叫 async 函式
fetchUserAndOrders(123)
.then(data => console.log('資料:', data))
.catch(err => process.exit(1));
Promise 進階技巧
// Promise 工具方法比較
// Promise.all - 全部成功才繼續,任一失敗即中止
const [users, posts] = await Promise.all([
fetchUsers(), // 同時開始
fetchPosts() // 同時開始
]);
// Promise.allSettled - 不管成功失敗,等全部完成
const results = await Promise.allSettled([
fetch('https://api1.example.com'),
fetch('https://api2.example.com'),
fetch('https://api3.example.com') // 即使這個失敗
]);
const succeeded = results.filter(r => r.status === 'fulfilled');
// Promise.race - 只取最快完成的那個
const fastest = await Promise.race([
fetchFromServer1(),
fetchFromServer2(),
new Promise((_, reject) => setTimeout(() => reject(new Error('逾時')), 5000))
]);
// Promise.any - 只取第一個成功的 (Node.js 15+)
const firstSuccess = await Promise.any([
tryServer1(),
tryServer2(),
tryServer3()
]);
REST API 開發
REST (Representational State Transfer) 是目前最廣泛使用的 API 設計風格。以下是一個包含認證機制的完整 CRUD API 範例。
GET
讀取資源,不修改任何資料。可快取、可書籤。
POST
建立新資源,請求 Body 包含新資料內容。
PUT / PATCH
PUT 完整替換,PATCH 部分更新。
DELETE
刪除指定資源,成功回傳 204 No Content。
完整 REST API 範例(含 JWT 認證)
// 📁 index.js - 完整 REST API
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
const db = { users: [], nextId: 1 };
const JWT_SECRET = process.env.JWT_SECRET || 'change-this-in-production';
// POST /api/auth/register - 使用者註冊
app.post('/api/auth/register', async (req, res) => {
try {
const { name, email, password } = req.body;
if (!name || !email || !password)
return res.status(400).json({ success: false, message: '請填寫所有欄位' });
if (password.length < 8)
return res.status(400).json({ success: false, message: '密碼至少 8 個字元' });
if (db.users.find(u => u.email === email))
return res.status(409).json({ success: false, message: 'Email 已被使用' });
const hashedPassword = await bcrypt.hash(password, 12);
const user = { id: db.nextId++, name, email, password: hashedPassword, role: 'user' };
db.users.push(user);
const token = jwt.sign({ id: user.id, role: user.role }, JWT_SECRET, { expiresIn: '7d' });
const { password: _, ...safeUser } = user;
res.status(201).json({ success: true, data: { user: safeUser, token } });
} catch (err) {
res.status(500).json({ success: false, message: '伺服器錯誤' });
}
});
// POST /api/auth/login - 使用者登入
app.post('/api/auth/login', async (req, res) => {
const { email, password } = req.body;
const user = db.users.find(u => u.email === email);
if (!user || !(await bcrypt.compare(password, user.password)))
return res.status(401).json({ success: false, message: '帳號或密碼錯誤' });
const token = jwt.sign({ id: user.id, role: user.role }, JWT_SECRET, { expiresIn: '7d' });
const { password: _, ...safeUser } = user;
res.json({ success: true, data: { user: safeUser, token } });
});
// 認證中介軟體
function auth(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: '請先登入' });
try {
req.user = jwt.verify(token, JWT_SECRET);
next();
} catch {
res.status(403).json({ error: '令牌無效或已過期' });
}
}
// GET /api/me - 取得目前登入使用者資訊
app.get('/api/me', auth, (req, res) => {
const user = db.users.find(u => u.id === req.user.id);
const { password: _, ...safeUser } = user;
res.json({ success: true, data: safeUser });
});
app.listen(3000, () => console.log('🚀 API 啟動於 http://localhost:3000'));
API 測試 (使用 curl)
# 1. 註冊
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"陳小明","email":"[email protected]","password":"pass1234"}'
# 2. 登入並取得 token
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"pass1234"}'
# 3. 使用 token 取得個人資訊
curl http://localhost:3000/api/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
{ success: true, data: {...} } 或 { success: false, message: "..." },方便前端統一處理。WebSocket 即時通訊
WebSocket 提供全雙工通訊通道,讓伺服器可主動推送資料給客戶端,非常適合即時聊天、通知、遊戲等應用。
npm install ws。如需更完整功能(自動重連、命名空間等),推薦使用 socket.io。WebSocket 即時聊天室
// 📁 chat-server.js - WebSocket 聊天室伺服器
const http = require('http');
const WebSocket = require('ws');
const server = http.createServer();
const wss = new WebSocket.Server({ server });
const clients = new Map(); // ws => { id, username }
wss.on('connection', (ws, req) => {
const clientId = Date.now().toString(36);
clients.set(ws, { id: clientId, username: null });
console.log(`✅ 新連線: ${clientId}`);
ws.send(JSON.stringify({ type: 'welcome', id: clientId, message: '歡迎進入聊天室!' }));
ws.on('message', (rawData) => {
try {
const msg = JSON.parse(rawData.toString());
const client = clients.get(ws);
if (msg.type === 'join') {
client.username = msg.username;
broadcast({ type: 'system', text: `👋 ${msg.username} 加入了聊天室`, count: clients.size }, ws);
} else if (msg.type === 'chat') {
broadcast({
type: 'chat',
id: client.id,
username: client.username || '匿名',
text: msg.text,
time: new Date().toLocaleTimeString('zh-TW')
});
}
} catch (e) { console.error('訊息解析錯誤', e); }
});
ws.on('close', () => {
const client = clients.get(ws);
if (client?.username) broadcast({ type: 'system', text: `${client.username} 已離開` });
clients.delete(ws);
console.log(`❌ 連線關閉: ${client?.id}`);
});
ws.on('error', (err) => console.error('連線錯誤:', err));
});
function broadcast(data, exclude = null) {
const msg = JSON.stringify(data);
for (const [ws] of clients) {
if (ws !== exclude && ws.readyState === WebSocket.OPEN) ws.send(msg);
}
}
server.listen(8080, () => console.log('💬 WebSocket 伺服器啟動於 ws://localhost:8080'));
前端客戶端範例
// 瀏覽器端 WebSocket 客戶端
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('✅ 已連接到伺服器');
// 加入聊天室
ws.send(JSON.stringify({ type: 'join', username: '陳小明' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'chat') {
console.log(`${data.username}: ${data.text}`);
} else if (data.type === 'system') {
console.log(`[系統] ${data.text}`);
}
};
ws.onclose = () => console.log('❌ 連線已中斷');
ws.onerror = (err) => console.error('WebSocket 錯誤:', err);
// 傳送訊息
function sendMessage(text) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'chat', text }));
}
}
資料庫整合
Node.js 可以輕鬆整合各種資料庫。以下介紹最常用的 MongoDB(NoSQL)與 MySQL(關聯式)的使用方式。
// 📁 models/User.js - Mongoose 模型定義
const mongoose = require('mongoose');
// 連接 MongoDB
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp')
.then(() => console.log('✅ MongoDB 連接成功'))
.catch(err => console.error('❌ 連接失敗:', err));
// Schema 定義
const userSchema = new mongoose.Schema({
name: { type: String, required: [true, '姓名必填'], trim: true, maxlength: 50 },
email: { type: String, required: true, unique: true, lowercase: true },
password: { type: String, required: true, minlength: 8, select: false },
role: { type: String, enum: ['user', 'admin'], default: 'user' },
isActive: { type: Boolean, default: true }
}, { timestamps: true }); // 自動加入 createdAt, updatedAt
// 索引
userSchema.index({ email: 1 });
userSchema.index({ createdAt: -1 });
const User = mongoose.model('User', userSchema);
// CRUD 操作
async function examples() {
// 新增
const user = await User.create({ name: '陳小明', email: '[email protected]', password: 'hashed_pw' });
// 查詢 (帶分頁)
const users = await User.find({ isActive: true })
.select('-password') // 排除密碼欄位
.sort('-createdAt') // 最新優先
.limit(10).skip(0);
// 更新並回傳更新後資料
const updated = await User.findByIdAndUpdate(
user._id,
{ $set: { name: '陳大明' } },
{ new: true, runValidators: true }
);
// 聚合查詢統計各角色人數
const stats = await User.aggregate([
{ $match: { isActive: true } },
{ $group: { _id: '$role', count: { $sum: 1 } } }
]);
console.log('角色統計:', stats);
}
// 📁 db/mysql.js - MySQL 連接池
const mysql = require('mysql2/promise');
// 建立連接池 (推薦,效能更好)
const pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'myapp',
waitForConnections: true,
connectionLimit: 10,
charset: 'utf8mb4'
});
// 查詢函式封裝
async function query(sql, params = []) {
const [rows] = await pool.execute(sql, params);
return rows;
}
// CRUD 操作
async function getUsers(page = 1, limit = 10) {
const offset = (page - 1) * limit;
return query('SELECT id, name, email, role FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?', [limit, offset]);
}
async function getUserById(id) {
const rows = await query('SELECT * FROM users WHERE id = ?', [id]);
return rows[0] || null;
}
async function createUser(name, email, hashedPassword) {
const result = await query(
'INSERT INTO users (name, email, password) VALUES (?, ?, ?)',
[name, email, hashedPassword]
);
return result.insertId;
}
async function updateUser(id, updates) {
const fields = Object.keys(updates).map(k => `${k} = ?`).join(', ');
const values = [...Object.values(updates), id];
return query(`UPDATE users SET ${fields} WHERE id = ?`, values);
}
// 交易 (Transaction)
async function transferBalance(fromId, toId, amount) {
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
await conn.execute('UPDATE accounts SET balance = balance - ? WHERE id = ?', [amount, fromId]);
await conn.execute('UPDATE accounts SET balance = balance + ? WHERE id = ?', [amount, toId]);
await conn.commit();
console.log('✅ 轉帳成功');
} catch (err) {
await conn.rollback();
console.error('❌ 交易失敗,已回滾:', err);
throw err;
} finally {
conn.release();
}
}
module.exports = { query, getUsers, getUserById, createUser, updateUser, transferBalance };
// 📁 cache/redis.js - Redis 快取
const { createClient } = require('redis');
const client = createClient({ url: process.env.REDIS_URL || 'redis://localhost:6379' });
client.on('error', err => console.error('Redis 錯誤:', err));
client.on('connect', () => console.log('✅ Redis 連接成功'));
await client.connect();
// API 回應快取裝飾器
function cacheMiddleware(ttl = 60) {
return async (req, res, next) => {
const key = `cache:${req.method}:${req.url}`;
const cached = await client.get(key);
if (cached) {
console.log('⚡ 快取命中:', key);
return res.json(JSON.parse(cached));
}
// 攔截 res.json 以快取回應
const originalJson = res.json.bind(res);
res.json = (data) => {
client.setEx(key, ttl, JSON.stringify(data));
return originalJson(data);
};
next();
};
}
// 使用範例
app.get('/api/products', cacheMiddleware(300), async (req, res) => {
const products = await Product.find(); // 首次查詢資料庫
res.json(products); // 後續 5 分鐘內直接回傳快取
});
// Session 儲存
await client.setEx(`session:${userId}`, 86400, JSON.stringify(sessionData));
const session = JSON.parse(await client.get(`session:${userId}`));
// 計數器 (限流)
const requests = await client.incr(`ratelimit:${ip}`);
if (requests === 1) await client.expire(`ratelimit:${ip}`, 60);
if (requests > 100) return res.status(429).json({ error: '請求過於頻繁' });
部署與最佳實踐
將 Node.js 應用程式部署到生產環境時,需要考慮效能、安全性、監控與可靠性。
環境變數管理 (.env)
// 📁 .env
PORT=3000
NODE_ENV=production
MONGODB_URI=mongodb+srv://user:[email protected]/myapp
JWT_SECRET=your-super-secret-jwt-key-here-min-32-chars
REDIS_URL=redis://localhost:6379
// 📁 config/env.js
require('dotenv').config();
module.exports = {
port: parseInt(process.env.PORT) || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
mongoUri: process.env.MONGODB_URI,
jwtSecret: process.env.JWT_SECRET || (() => {
if (process.env.NODE_ENV === 'production')
throw new Error('❌ 生產環境必須設定 JWT_SECRET');
return 'dev-secret-only';
})(),
isProduction: process.env.NODE_ENV === 'production'
};
PM2 程序管理
# 安裝 PM2
npm install -g pm2
# 啟動應用程式 (叢集模式,充分利用多核心 CPU)
pm2 start index.js --name "my-app" -i max
# 常用指令
pm2 list # 查看所有程序
pm2 logs my-app # 查看日誌
pm2 monit # 即時監控 CPU/記憶體
pm2 restart my-app # 重啟
pm2 stop my-app # 停止
pm2 delete my-app # 刪除
# 儲存程序清單 (系統重啟後自動還原)
pm2 save
pm2 startup
Docker 容器化
# 📁 Dockerfile - 多階段建構
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine
WORKDIR /app
# 建立非 root 使用者 (安全)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER appuser
EXPOSE 3000
# 健康檢查
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "index.js"]
安全性最佳實踐
使用 Helmet 保護 HTTP 標頭
npm install helmet 然後 app.use(helmet()),自動設定安全性相關 HTTP 標頭,防止常見攻擊。
速率限制 (Rate Limiting)
使用 express-rate-limit 防止暴力破解與 DDoS 攻擊,限制每個 IP 的請求頻率。
輸入驗證與消毒
使用 joi 或 express-validator 驗證所有使用者輸入,防止 SQL Injection 與 XSS 攻擊。
HTTPS 與安全連線
生產環境必須使用 HTTPS。可使用 Let's Encrypt 免費憑證,搭配 Nginx 反向代理。
定期更新相依套件
使用 npm audit 定期檢查安全漏洞,並使用 npm audit fix 修復。
效能優化建議
// 1. 使用壓縮中介軟體
const compression = require('compression');
app.use(compression());
// 2. 啟用 cluster 模式 (充分利用多核心)
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const cpuCount = os.cpus().length;
console.log(`啟動 ${cpuCount} 個 Worker 程序`);
for (let i = 0; i < cpuCount; i++) cluster.fork();
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.id} 已退出,重新啟動...`);
cluster.fork();
});
} else {
require('./app'); // 啟動 Express 應用
}
// 3. 適當使用快取
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 }); // 10 分鐘
app.get('/api/config', (req, res) => {
const cached = cache.get('app-config');
if (cached) return res.json(cached);
const config = loadConfigFromDB();
cache.set('app-config', config);
res.json(config);
});