本文 AI 產出,尚未審核
ExpressJS (TypeScript) – 檔案上傳:Multer 基礎使用
簡介
在 Web 應用程式中,檔案上傳 是最常見的需求之一,無論是使用者上傳頭像、文件或是商品圖片,都離不開伺服器端的接收與處理。對於使用 ExpressJS 搭配 TypeScript 的開發者來說,Multer 是官方推薦的中介軟體(middleware),它能簡潔地將 multipart/form-data 解析成可操作的檔案物件,並提供豐富的驗證、限制與儲存設定。
本篇文章將從 Multer 的安裝、基本設定、實作範例 逐步說明,並針對常見的坑洞與最佳實踐提供建議,讓你能在專案中快速且安全地完成檔案上傳功能。
核心概念
1. 為什麼選擇 Multer?
- 輕量且與 Express 完全相容:只需要在路由上掛載即可。
- 支援多種儲存方式:本機磁碟、記憶體、甚至自訂的雲端儲存。
- 內建檔案大小、類型限制,減少惡意上傳的風險。
2. 安裝與型別宣告
npm i multer
npm i -D @types/multer # TypeScript 用的型別定義
Tip:若使用
ts-node-dev或nodemon,請確保tsconfig.json的esModuleInterop為true,這樣才能以import multer from 'multer'的寫法匯入。
3. 基本使用流程
- 建立 Multer 實例(決定檔案儲存位置與檔名)
- 在路由上掛載(使用
upload.single('fieldName')、upload.array()或upload.fields()) - 在控制器中取得檔案資訊(
req.file或req.files)
下面分別用三種常見的上傳方式示範。
程式碼範例
範例 1:單一檔案上傳(single)
// src/middleware/upload.ts
import multer from 'multer';
import path from 'path';
// 設定儲存目錄與檔名
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, path.resolve(__dirname, '../../uploads'));
},
filename: (req, file, cb) => {
// 例:20231125-163045-原檔名.jpg
const timestamp = Date.now();
const ext = path.extname(file.originalname);
cb(null, `${timestamp}-${file.fieldname}${ext}`);
},
});
// 檔案類型過濾(只接受圖片)
const fileFilter = (req: Express.Request, file: Express.Multer.File, cb: multer.FileFilterCallback) => {
if (/image\/(jpeg|png|gif)/.test(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('只接受 JPEG、PNG、GIF 圖片'), false);
}
};
export const upload = multer({
storage,
fileFilter,
limits: { fileSize: 2 * 1024 * 1024 }, // 2 MB
});
// src/routes/user.ts
import { Router } from 'express';
import { upload } from '../middleware/upload';
const router = Router();
/**
* POST /profile/avatar
* 上傳單一使用者頭像
*/
router.post('/profile/avatar', upload.single('avatar'), (req, res) => {
// `req.file` 為單一檔案資訊
if (!req.file) return res.status(400).json({ message: '未收到檔案' });
res.json({
message: '上傳成功',
filePath: `/uploads/${req.file.filename}`,
});
});
export default router;
說明:
upload.single('avatar')只會接受欄位名稱為avatar的檔案,若上傳多個檔案則只會保留第一個。
範例 2:多檔案上傳(array)
// src/routes/gallery.ts
import { Router } from 'express';
import { upload } from '../middleware/upload';
const router = Router();
/**
* POST /gallery/photos
* 同時上傳最多 5 張照片
*/
router.post('/gallery/photos', upload.array('photos', 5), (req, res) => {
const files = req.files as Express.Multer.File[];
if (!files || files.length === 0) {
return res.status(400).json({ message: '未收到任何檔案' });
}
const paths = files.map(f => `/uploads/${f.filename}`);
res.json({ message: '上傳成功', paths });
});
export default router;
技巧:
array('photos', 5)限制一次最多 5 個檔案,超過會自動拋出 MulterError,之後可在全域錯誤處理器捕獲。
範例 3:多欄位混合上傳(fields)
// src/routes/product.ts
import { Router } from 'express';
import { upload } from '../middleware/upload';
const router = Router();
/**
* POST /product/create
* - `thumbnail` : 單一商品縮圖
* - `gallery` : 多張商品圖(最多 8 張)
*/
router.post(
'/product/create',
upload.fields([
{ name: 'thumbnail', maxCount: 1 },
{ name: 'gallery', maxCount: 8 },
]),
(req, res) => {
const thumbnail = (req.files as any).thumbnail?.[0];
const gallery = (req.files as any).gallery || [];
if (!thumbnail) return res.status(400).json({ message: '缺少縮圖' });
res.json({
message: '商品建立成功',
thumbnail: `/uploads/${thumbnail.filename}`,
gallery: gallery.map((f: Express.Multer.File) => `/uploads/${f.filename}`),
});
},
);
export default router;
重點:
upload.fields()允許同時接收不同欄位的檔案,回傳的req.files會是一個以欄位名稱為鍵的陣列集合。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 未設定檔案大小限制 | 大檔案會占用過多記憶體或磁碟,甚至導致服務掛掉。 | 使用 limits.fileSize 參數。 |
| MulterError 未被捕獲 | 上傳失敗時會拋出 MulterError,若未處理會直接回傳 500。 |
在全域錯誤處理器中檢查 err instanceof multer.MulterError。 |
| 檔名衝突 | 直接使用原始檔名會導致覆寫。 | 透過時間戳、UUID 或 hash 產生唯一檔名。 |
| 資料夾不存在 | diskStorage 的 destination 若指向不存在的資料夾會拋錯。 |
在啟動時使用 fs.mkdirSync(..., { recursive: true }) 建立目錄。 |
| 記憶體儲存的檔案未釋放 | storage: multer.memoryStorage() 會把檔案留在 RAM,若忘記轉存會浪費資源。 |
完成上傳後立即將緩衝區寫入雲端或磁碟,並釋放變數。 |
建議的最佳實踐
- 統一錯誤回應格式:將 MulterError 包裝成
{ code, message },前端才能友好顯示。 - 驗證檔案類型:僅允許必要的 MIME,搭配
fileFilter防止惡意檔案。 - 分層儲存目錄:例如
uploads/2023/11/25/,有助於後續清理與備份。 - 安全性檢查:上傳完畢後,使用
sharp、file-type等套件再次確認檔案內容。 - 使用環境變數:將上傳路徑、大小上限等設定抽離至
.env,提升部署彈性。
實際應用場景
| 場景 | 需求 | Multer 設定範例 |
|---|---|---|
| 使用者個人檔案 | 單一頭像、大小 ≤ 1 MB、只接受 JPEG/PNG | upload.single('avatar') + fileFilter + limits.fileSize = 1MB |
| 商品上架 | 縮圖 + 多張展示圖(最多 10 張) | upload.fields([...]),分別設定 maxCount |
| 文件審核系統 | PDF、DOCX,大小上限 5 MB,暫存於記憶體後上傳至 S3 | memoryStorage + fileFilter(檢查 application/pdf、application/vnd.openxmlformats-officedocument.wordprocessingml.document) |
| 即時聊天 | 短暫圖片(如表情)直接放在記憶體,回傳 Base64 給前端 | memoryStorage + limits.fileSize = 200KB |
總結
Multer 為 Express + TypeScript 生態系中最成熟、最彈性的檔案上傳解決方案。只要掌握以下幾點,就能在專案裡安全、有效地處理各種上傳需求:
- 建立適當的 storage(磁碟或記憶體)並自訂檔名。
- 使用
fileFilter+limits防止不合法或過大的檔案。 - 選擇正確的 API:
single、array、fields,依需求掛載於路由。 - 在全域錯誤處理器捕獲 MulterError,提供一致的回應格式。
- 遵循安全與維運最佳實踐(目錄分層、環境變數、二次驗證)。
透過本文提供的範例與建議,你可以在 ExpressJS (TypeScript) 專案中快速實作檔案上傳功能,且兼具可讀性、可維護性與安全性。祝開發順利,玩得開心!