[筆記] 使用 Multer 實作大頭貼上傳(Part 1)
在開發網路應用程式時,上傳檔案是個常見的功能。最近學到透過 Multer 套件,讓客戶端能上傳文件到伺服器端。本篇將紀錄使用 Multer 處理大頭貼上傳,及應用另一個套件 Sharp 加工圖片的筆記。
Multer
Multer 是由推出 Express 的同一間公司所提供的第三方套件,專門用來處理文件上傳到伺服器。👉 npm 👉GitHub
專案介紹
這次預期的功能,是透過建立三條路由,讓使用者可以上傳、刪除、讀取大頭照 Profile Picture:
- 使用者需要經過驗證,才能上傳和刪除大頭照 — 透過
auth
Middleware 驗證後,含有用戶資訊的物件,將被儲存在req.user
上供後續使用 - 借助 Mongoose ODM,將使用者上傳的照片儲存在 MongoDB 資料庫當中
初始化專案
安裝 Multer
$ npm i multer
在 Node.js 載入 Multer
const multer = require(‘multer’)
設定 Multer
const upload = multer(opts)
透過 multer(opts)
設定 Multer ,並將回傳的實例(instance)存在 upload
上,供後續使用— multer()
接受一個 opts
物件,它帶有許多可以用來設定的「選項」:
限制上傳檔案的大小:limits
limit
選項帶有一個物件,可以用來限制以下各屬性的大小
為避免使用者上傳的檔案過大,在專案中限制上傳的檔案不得超過 1MB:
fileSize
:限制上傳檔案的大小fileSize
接受的單位為 bytes:1MB = 1000000 bytes
限制接受的上傳格式:fileFilter
fileFilter
選項帶有一個函式,用來篩選符合條件的上傳檔案,其接受三個參數: request
物件、帶有上傳檔案資訊的file
物件、篩選完成後呼叫的cb
函式。
👉 file
會依照不同的設定,帶有以下特定的屬性資訊:
👉 cb()
是一個當篩選完成時被呼叫 Callback 函式,其接受兩個參數:(1)錯誤訊息 (2)說明是否接受該檔案的 Boolean 值
- 若接受該檔案:呼叫時帶入
true
cb(null, true)
2. 若不接受該檔案:呼叫時帶入 false
cb(null, false)
3. 輸出錯誤訊息
cb(new Error('請上傳正確的檔案格式'))
在專案中,由於要請使用者上傳的是封面照,因此透過 fileFilter()
函式篩選時,只接受三種檔案格式:jpg、png、jpeg — 若篩選失敗,丟出錯誤訊息:
originalname
:獲得檔案的原始名稱(名稱+檔案格式).match
:確認檔案格式是篩選時所設定的三種檔案格式之一cb(new Error())
:檔案不符合格式時,觸發cb()
函式,帶入錯誤訊息cb(null, true)
:檔案符合格式時,觸發cb()
函式,帶入true
值
在路由中接收上傳檔案
透過 multer(opts)
設定所回傳的實例(instance)上,帶有不同種方法可以用來處理檔案的接收,常見的幾個方法:
.single(fieldname)
接收來自名為 fieldname
欄位的「單一」上傳檔案,並將檔案資訊存放在 req.file
上。例如:
// 接收名為 avatar 欄位的單一檔案
upload.single(‘avatar’)
.array(fieldname[, maxCount])
接收來自名為 fieldname
欄位的「多個」上傳檔案,且能透過第二個參數 maxCount
限制接收的最高數量,最終將包含所有檔案資訊的陣列存放在 req.files
上。例如:
// 接收至多 12 個名為 avatar 欄位的檔案
upload.array('avatars', 12)
.fields(fields)
接收來自不同欄位的上傳檔案,其中透過 fields
這個陣列來設定 — fields
包含了一個個「欄位名稱+限制數量」的物件 — 最終將包含所有檔案資訊的陣列以物件的形式,存放在 req.files
上。例如:
// 接收名為 avatar 和 gallery 欄位的檔案,分別接受最多 1 個和 8 個檔案
upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
嵌入接收方法
在專案中,只接收一個來自名為 avatar
欄位,且檔案格式為 jpg/jpeg/png 的圖片,因此將使用 upload.single(‘avatar’)
作為 Middleware ,帶入路由中,來處理檔案接收,運作流程大致如下:
- 來自客戶端的請求透過
POST
方法/users/me/avatar
路徑進來 - Multer 將到請求中,尋找來自名為
avatar
欄位的上傳檔案 - Multer 確認該檔案符合「檔案大小」及「檔案格式」的篩選條件後,將檔案資訊儲存在
req.file
上,供後續使用
將照片存至資料庫
新增欄位
要儲存用戶大頭貼到資料庫以前,需要先修改userSchema
:新增一個用來存放大頭貼的avatar
欄位,其欄位類型為Buffer
— 可儲存二進位的照片資料
儲存資料
在 userSchema
中新增好存放大頭貼的欄位後,就能在路由中,撰寫程式碼,將用戶大頭貼存到資料庫中:
req.user
是在透過auth
Middleware 驗證使用者後,從資料庫獲取的用戶物件:存入大頭貼前,該物件上avatar
屬性是空值req.file
是該檔案經 Multer 驗證和處理後,存放檔案資訊的地方:在此將存有二進位照片資料的buffer
存放在該用戶的avatar
欄位中req.user.save()
:請 Mongoose 將包含大頭貼的用戶資料存至資料庫中
❗️File 物件屬性:
前面有提到 File 上的屬性會因為 Multer 設定的不同而有所差異。若設定 multer() 時,opt 物件沒有透過 dest / storage 屬性指定儲存方式,帶有照片的 Buffer 將被存在 req.file 上,供後續使用;相反的,req.file 將不會帶有 buffer 屬性資訊
測試功能
透過 Postman 上傳一張封面照到此路徑測試:
- 傳輸的資料類型設定為:
form-data
- KEY 左側設定為正確的欄位名稱:
avatar
- KEY 右側設定為正確的類型:
file
- VALUE 選擇要上傳的檔案
- 透過 Send 發送請求,並確認 Status code 為 200 OK:代表成功
❗️設定表單傳輸類型👉 透過表單上傳文件,需設定 <form> 的 enctype 屬性為: multipart/form-data
👉 enctype 屬性:設定提交表單到伺服器前,應對表單資料如何進行編譯
👉 multipart/form-data: 讓 Server 知道表單不僅包含純文字,也包含二進位資料
👉 若無提交檔案,可用 enctype 屬性預設:application/x-www-form-urlencoded
👉 Multer 會在 request 中尋找帶有此表單類型的資料
確認結果
在 Robo 3T 當中,可以確認該使用者 Document 的 avatar
欄位已經存有大頭貼的二進位資料了:
將照片從資料庫刪除
透過 DELETE
方法向 /users/me/avatar
路徑發出請求,刪除使用者大頭貼:
- 使用者需要經過驗證,才能刪除大頭照 — 透過
auth
Middleware 驗證後,已經獲得req.user
能直接使用 — 將使用者avatar
欄位設定為null
- 請 Mongoose 將更新後的使用者資料儲存到 MongoDB 資料庫中
- 若過程中發生錯誤,Status Code 設定為 500 (伺服器發生錯誤)回傳
在瀏覽器中讀取大頭照
在瀏覽器透過 GET
方法向 /users/:id/avatar
路徑發出請求,並帶入使用者 id
,以獲取該名使用者的大頭貼,在瀏覽器中呈現:
- 透過使用者
id
到資料庫尋找該名使用者資料 - 若無找到該名使用者,或該名使用者無大頭貼時,丟出錯誤
- 回傳大頭貼時,需要設定
Header
當中的Content-type
,定義回傳的資料格式為 png 的圖片檔 - 若
catch
接收到錯誤,設定 Status Code 為 404 (伺服器找不到該資源)回傳
測試功能
在瀏覽器中,透過 GET
方法向 /users/:id/avatar
路徑發出請求,並帶入使用者id
,能正確讀取大頭照:
錯誤處理
前面已經透過許多設定,請 Multer 先行協助處理上傳的檔案,且遇到上傳失敗時,丟出錯誤訊息。然而,目前一旦發生上傳失敗的狀況 — 例如上傳一個超過 1MB 的檔案 — 伺服器回傳的會是 HTML 格式的錯誤內容,其中當然包含從 Multer 丟出的錯誤訊息:
修改錯誤回傳
透過預設的錯誤處理,將伺服器將回傳 HTML 格式的內容,然而在專案中,想要透過修改錯誤處理,回傳 JSON 格式的錯誤訊息,解決的方式:「在路由的最後,撰寫一個接收和處理錯誤訊息的 Middleware」詳細介紹可以參考 👉Express 官方文件
處理錯誤訊息的 Middleware 範例如下,其帶有四個參數 — 除了 req
、res
、next
三個參數外,也必須帶有 err
參數來接收錯誤訊息:
嵌入錯誤處理
在路由的最後,加入錯誤處理的 Middleware: Status Code 設定為 400,並回傳 JSON 格式的錯誤訊息:
測試功能
透過 Postman 上傳一張超過 1MB 的圖片到這條路徑,並確認回傳的 Status Code 是 400,且 Body 是一個 JSON 格式的錯誤訊息:
Sharp
Sharp 是一個第三方套件,用來處理圖像,例如:轉換圖片的尺寸和格式等。 Sharp 支援的格式有:JPEG, PNG, WebP, TIFF, GIF and SVG 。
專案介紹
透過本篇上半部的專案,成功讓使用者可以上傳、刪除、讀取大頭照 Profile Picture。接下來將透過 Sharp 模組,優化「負責上傳的路由」 — 將使用者上傳的圖片,先行轉換成統一的尺寸和檔案,再存至資料庫當中。
安裝 Sharp
$ npm i sharp
在 Node.js 載入 Sharp
const sharp = require('sharp')
Sharp 實例
在使用 Sharp 時,首先需要先將圖片(input)帶入 sharp()
函式中使用:
input
參數:可以接受含有圖片資訊的 Buffer 或圖片路徑的字串sharp()
回傳:Sharp
實例 — 透過此實例上的許多函式方法,可以為圖片進行一連串的編輯、修改、加工
resize() 方法
resize()
是 Sharp
實例上的方法,其帶有三個參數:
width
:預期修正後的寬height
:預期修正後的高options
:帶有其他設定選項的物件。詳細選項可以參考👉官方文件
在專案中,透過使用 resize()
方法,並帶入 options
物件,設定預期產出的寬和高皆為 50 px:
png() 方法
Sharp
實例上有許多轉換圖片格式的方法 — png()
函式能將圖片轉成 png 格式。其帶有一個options
參數,詳細選項可以參考👉官方文件:
toBuffer() 方法
為方便將修改後的圖片存取到資料庫當中,最後透過 toBuffer()
方法,將 50x50 png 格式的圖片,轉成 Buffer
— 此方法回傳的是一個 Promise:
將圖片存至資料庫
編輯後的圖片將被存放在 buffer
上。透過新增使用者 avatar
欄位的資訊後,就能請 Mongoose 協助將使用者資料存至資料庫當中:
測試功能
在瀏覽器中,透過 GET
方法向 /users/:id/avatar
路徑發出請求,並帶入使用者id
,就能正確讀取修改後的大頭照:
結語
本篇實作中,建立了三條路由來製作大頭貼的上傳、刪除、讀取功能,並借助 Multer 第三方套件的功能,來處理使用者上傳的檔案。且為了使大頭貼在儲存至資料庫以前,能有一致的圖片尺寸與格式,額外使用了 Sharp 套件,為大頭貼進行加工。
在實作的過程中,會發現 Multer 和 Sharp 提供的功能真的很多 — 例如 Multer 還提供其他儲存檔案的方式;Sharp 也提供多種圖片加工的方式。迫不及待有更多的機會可以去挖掘和嘗試!