Next.js – 連接MySQL
建議了解以下文章內容後再往下讀:
https://hyc.eshachem.com/program/js-callback-hell/
https://hyc.eshachem.com/program/js-defensive-function/
https://hyc.eshachem.com/program/tcp-keep-alive/
我最近都在用Next.js進行全端開發,想當然爾第一個遇到的問題就是:怎麼接DB?因此這裏提供解法並詳細說明我後來的寫法~!
這裏先show出程式碼:
import mysql from "mysql2/promise";
const globalForMysql = globalThis as unknown as {
__mysqlPool: mysql.Pool | undefined;
};
function getRequiredEnv(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required env: ${name}`);
}
return value;
}
function createNewPool() {
return mysql.createPool({
host: getRequiredEnv("DB_HOST"),
port: Number(process.env.DB_PORT || 3306),
user: getRequiredEnv("DB_USER"),
password: getRequiredEnv("DB_PASSWORD"),
database: getRequiredEnv("DB_NAME"),
waitForConnections: true,
connectionLimit: 5,
queueLimit: 0,
enableKeepAlive: true,
keepAliveInitialDelay: 10000,
});
}
export const pool =
process.env.NODE_ENV !== "production"
? globalForMysql.__mysqlPool || (globalForMysql.__mysqlPool = createNewPool())
: createNewPool();Code language: JavaScript (javascript)
基礎設定
接着再來逐行解釋如下:
import mysql from "mysql2/promise";Code language: JavaScript (javascript)
- 用的是
mysql2套件的 Promise 版本。 - 傳統的
mysql2是使用「回呼函式 (Callback)」,寫久了會變成 Callback Hell。引入promise版本後,我們就可以在 Route Handler 中優雅地使用async/await來處理非同步的資料庫操作。
const globalForMysql = globalThis as unknown as {
__mysqlPool: mysql.Pool | undefined;
};Code language: JavaScript (javascript)
globalThis是一個 JavaScript 內建的全域物件(在 Node.js 環境指的就是global,在瀏覽器指的就是window)。as unknown as { __mysqlPool: ... }使用「雙重型別斷言(Type Assertion)」( 先轉成最寬鬆的unknown,再強制轉型成「含有__mysqlPool屬性(其型別可能是mysql.Pool或undefined)的物件」,好讓 TypeScript 閉嘴)因為 JavaScript 的globalThis預設並沒有__mysqlPool這個屬性,如果你直接寫globalThis.__mysqlPool,TypeScript 會噴錯。
function getRequiredEnv(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required env: ${name}`);
}
return value;
}Code language: JavaScript (javascript)
定義一個名為 getRequiredEnv 的防禦型函式,接收一個字串參數 name(環境變數的名稱),並保證回傳的一定是字串
建立連線池(Connection Pool)
function createNewPool() {
return mysql.createPool({
host: getRequiredEnv("DB_HOST"),
port: Number(process.env.DB_PORT || 3306),
user: getRequiredEnv("DB_USER"),
password: getRequiredEnv("DB_PASSWORD"),
database: getRequiredEnv("DB_NAME"),
waitForConnections: true,
connectionLimit: 5, //設定連線最多數量
queueLimit: 0,
enableKeepAlive: true,
keepAliveInitialDelay: 10000,
});
}Code language: JavaScript (javascript)
建立「連線池(Pool)」,而非「單一連線(Connection)」。
連線池會幫你管理多個連線,重複利用,不會每次查詢都重新開關 TCP 連線(三向交握),效能好很多。
waitForConnections: true:當連線池裡的 5 個連線都被拿光了,下一個請求進來時,要「排隊等待」有人釋放連線,而不是直接報錯拒絕。queueLimit: 0:當連線滿了,排隊等待的請求數量「沒有上限」(0代表不限制)。enableKeepAlive: true與keepAliveInitialDelay: 10000:- 這是為了解決 Serverless 環境 或 防火牆 的閒置斷線問題。每隔 10000 毫秒(10 秒),連線池會自動向 MySQL 發送一個「心跳包(TCP Keep-Alive)」,告訴資料庫「我還活著,別把我斷線」,避免發生
ETIMEDOUT或PROTOCOL_CONNECTION_LOST的錯誤。
單例模式與環境分流
export const pool =
process.env.NODE_ENV !== "production"
? globalForMysql.__mysqlPool || (globalForMysql.__mysqlPool = createNewPool())
: createNewPool();Code language: JavaScript (javascript)
檢查如果不是非生產環境(也就是 development 開發環境或 test 測試環境)。
