本文 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-devnodemon,請確保 tsconfig.jsonesModuleInteroptrue,這樣才能以 import multer from 'multer' 的寫法匯入。

3. 基本使用流程

  1. 建立 Multer 實例(決定檔案儲存位置與檔名)
  2. 在路由上掛載(使用 upload.single('fieldName')upload.array()upload.fields()
  3. 在控制器中取得檔案資訊req.filereq.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,若忘記轉存會浪費資源。 完成上傳後立即將緩衝區寫入雲端或磁碟,並釋放變數。

建議的最佳實踐

  1. 統一錯誤回應格式:將 MulterError 包裝成 { code, message },前端才能友好顯示。
  2. 驗證檔案類型:僅允許必要的 MIME,搭配 fileFilter 防止惡意檔案。
  3. 分層儲存目錄:例如 uploads/2023/11/25/,有助於後續清理與備份。
  4. 安全性檢查:上傳完畢後,使用 sharpfile-type 等套件再次確認檔案內容。
  5. 使用環境變數:將上傳路徑、大小上限等設定抽離至 .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/pdfapplication/vnd.openxmlformats-officedocument.wordprocessingml.document
即時聊天 短暫圖片(如表情)直接放在記憶體,回傳 Base64 給前端 memoryStorage + limits.fileSize = 200KB

總結

Multer 為 Express + TypeScript 生態系中最成熟、最彈性的檔案上傳解決方案。只要掌握以下幾點,就能在專案裡安全、有效地處理各種上傳需求:

  1. 建立適當的 storage(磁碟或記憶體)並自訂檔名。
  2. 使用 fileFilter + limits 防止不合法或過大的檔案。
  3. 選擇正確的 APIsinglearrayfields,依需求掛載於路由。
  4. 在全域錯誤處理器捕獲 MulterError,提供一致的回應格式。
  5. 遵循安全與維運最佳實踐(目錄分層、環境變數、二次驗證)。

透過本文提供的範例與建議,你可以在 ExpressJS (TypeScript) 專案中快速實作檔案上傳功能,且兼具可讀性、可維護性與安全性。祝開發順利,玩得開心!