Multer یک middleware در Node.js برای مدیریت multipart/form-data
است که فرآیند آپلود فایلها در Node.js را سادهتر میکند. در این مقاله قصد داریم تا روش استفاده از Multer برای مدیریت فایلها و آپلود آنها در Node.js را بررسی کنیم.
وب اپلیکیشنها انواع مختلفی از ورودیها را از کاربران دریافت میکنند، از جمله متن، کنترلهای گرافیکی مانند چک باکس یا radio buttonها، و فایلهایی مانند تصاویر، ویدیوها و سایر رسانهها.
در فرمها، هر یک از این ورودیها به سروری ارسال میشود که ورودیها را پردازش میکند، آنها را برای موارد مختلف مورد استفاده قرار میدهد، شاید در جای دیگری ذخیره کرده و سپس response success
یا failed
را به فرانتاند ارسال میکند.
هنگام ارسال فرمهایی که حاوی ورودیهای متنی هستند، سرور (در مورد ما Node.js) کار کمتری برای انجام دادن دارد. با استفاده از Express، میتوانیم به راحتی تمام ورودیهای وارد شده را در آبجکت req.body
دریافت کنیم. با این حال، ارسال فرم همراه با فایلها کمی پیچیدهتر است، زیرا نیاز به پردازش بیشتری دارد. اینجاست که میتوانیم از Multer برای آپلود فایلها استفاده کنیم.
همه فرمها دارای یک ویژگی enctype
هستند که مشخص میکند چگونه دادهها باید قبل از ارسال به سرور توسط مرورگر کدگذاری شوند. مقدار پیش فرض application/x-www-form-urlencoded
است که از دادههای الفبایی عددی پشتیبانی میکند. نوع دیگر رمزگذاری multipart/form-data
است که شامل آپلود فایلها از طریق فرمها میباشد.
دو راه برای آپلود فرمها با رمزگذاری multipart/form-data
وجود دارد. اولین مورد استفاده از ویژگی enctype
است:
<form action='/upload_files' enctype='multipart/form-data'> ... </form>
کد بالا دادههای فرم را به مسیر /upload_files
برنامه ما ارسال میکند. مورد دوم با استفاده از API FormData
است. API FormData
به ما اجازه میدهد تا یک multipart/form-data
با جفتهای key-value بسازیم که میتواند به سرور ارسال شود. در ادامه نحوه استفاده از آن را داریم:
const form = new FormData() form.append('name', "Dillion") form.append('image', <a file>)
در ارسال چنین فرمهایی، تجزیه صحیح فرم و اجرای عملیات نهایی بر روی دادهها بر عهده سرور میباشد.
Multer یک middleware طراحی شده برای مدیریت دادههای multipart/form-data
در فرمها است. این middleware شبیه به body-parser محبوب Node.js میباشد که برای ارسال فرم در Express تعبیه شده است. تفاوت Multer در این است که از دادههای multipart پشتیبانی کرده و فقط فرمهای multipart/form-data
را پردازش میکند.
Multer کار body-parser را با پیوست کردن مقادیر فیلدهای متنی در آبجکت req.body
انجام میدهد. همچنین یک آبجکت جدید برای چندین فایل، req.file
یا req.files
ایجاد میکند که اطلاعات مربوط به آن فایلها را در خود نگه میدارد. از آبجکت فایل، میتوانیم هر اطلاعاتی را که برای ارسال فایل به یک API مدیریت رسانه، مانند Cloudinary لازم است، انتخاب نماییم.
اکنون که اهمیت Multer را درک کردهایم، یک برنامه کوچک میسازیم تا بررسی کنیم که چگونه یک برنامه فرانتاند میتواند سه فایل مختلف را همزمان در یک فرم ارسال کند؛ و Multer چگونه میتواند فایلهای موجود در بکاند را پردازش کرده و آنها را برای استفادههای بیشتر در دسترس قرار دهد.
ما کار خود را با ساختن فرانتاند با استفاده از vanilla HTML ، CSS و جاوااسکریپت شروع میکنیم. البته، به راحتی میتوانیم از هر فریمورک دیگری برای این کار استفاده نماییم.
ابتدا فولدری به نام file-upload-example
ایجاد میکنیم و سپس فولدر دیگری به نام frontend
در داخل آن میسازیم. در فولدر frontend، ما سه فایل استاندارد خواهیم داشت: index.html
، styles.css
و script.js
:
<!-- index.html --> <body> <div class="container"> <h1>File Upload</h1> <form id='form'> <div class="input-group"> <label for='name'>Your name</label> <input name='name' id='name' placeholder="Enter your name" /> </div> <div class="input-group"> <label for='files'>Select files</label> <input id='files' type="file" multiple> </div> <button class="submit-btn" type='submit'>Upload</button> </form> </div> <script src='./script.js'></script> </body>
توجه به این نکته لازم است که ما یک label و یک input برای Your Name
و Select Files
ایجاد کردهایم. همچنین یک دکمه Upload
نیز ساختهایم.
در مرحله بعد، برای استایلدهی به اپلیکیشن CSS را اضافه میکنیم:
/* style.css */ body { background-color: rgb(6, 26, 27); } * { box-sizing: border-box; } .container { max-width: 500px; margin: 60px auto; } .container h1 { text-align: center; color: white; } form { background-color: white; padding: 30px; } form .input-group { margin-bottom: 15px; } form label { display: block; margin-bottom: 10px; } form input { padding: 12px 20px; width: 100%; border: 1px solid #ccc; } .submit-btn { width: 100%; border: none; background: rgb(37, 83, 3); font-size: 18px; color: white; border-radius: 3px; padding: 20px; text-align: center; }
فرمی که ساختهایم دو ورودی دارد: name
و files
. ویژگی multiple
مشخص شده در ورودی files
ما را قادر میسازد تا چندین فایل را انتخاب کنیم.
سپس با استفاده از کد زیر فرم را به سرور ارسال مینماییم:
// script.js const form = document.getElementById("form"); form.addEventListener("submit", submitForm); function submitForm(e) { e.preventDefault(); const name = document.getElementById("name"); const files = document.getElementById("files"); const formData = new FormData(); formData.append("name", name.value); for(let i =0; i < files.files.length; i++) { formData.append("files", files.files[i]); } fetch("http://localhost:5000/upload_files", { method: 'POST', body: formData }) .then((res) => console.log(res)) .catch((err) => console.log("Error occured", err)); }
وقتی از script.js
استفاده میکنیم باید چندین اتفاق مهم بیفتد. ابتدا المنت form
را از DOM دریافت کرده و یک event submit
را به آن اضافه میکنیم. پس از ارسال، از preventDefault
استفاده میکنیم تا از اقدام پیشفرضی که مرورگر هنگام ارسال فرم انجام میدهد، جلوگیری نماییم. سپس، المنت ورودی name
و files
را از DOM دریافت میکنیم و formData
را میسازیم.
از اینجا، مقدار ورودی name را با استفاده از یک کلید name
به formData
میافزاییم. سپس، فایلهای متعددی را که انتخاب کردهایم به صورت داینامیک و با استفاده از یک کلید files
به formData
اضافه میکنیم.
* باید به این نکته توجه داشته باشیم که اگر فقط یک فایل داریم، میتوانیم files.files[0]
را اضافه نماییم.
در نهایت، ما یک درخواست POST
را به http://localhost:5000/upload_files
اضافه میکنیم، که API در بکاند است که در بخش بعدی آن را میسازیم.
برای نسخهی نمایشی اپلیکیشنی که داریم، با استفاده از Node.js و Express بکاند خود را میسازیم. ما یک API ساده در upload_files
ساخته و سرور خود را در localhost:5000
راهاندازی میکنیم. API یک درخواست POST
دریافت میکند که حاوی ورودیهای فرم ارسال شده میباشد.
برای استفاده از Node.js برای سرور خود، باید یک پروژه اصلی Node.js را راهاندازی کنیم. در فولدر اصلی پروژه در ترمینال در file-upload-example
، کد زیر را اجرا میکنیم:
npm init -y
دستور بالا یک package.json
بیسیک با اطلاعاتی در مورد برنامه ما ایجاد میکند. سپس، dependencyهای مورد نیاز را نصب میکنیم که برای پروژه ما Express است:
npm i express
سپس یک فایل server.js
میسازیم و کد زیر را به آن اضافه میکنیم:
// server.js const express = require("express"); const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.post("/upload_files", uploadFiles); function uploadFiles(req, res) { console.log(req.body); } app.listen(5000, () => { console.log(`Server started...`); });
Express حاوی آبجکت bodyParser
است که یک middleware برای پر کردن req.body
با ورودیهای ارسال شده در یک فرم میباشد. فراخوانی app.use(express.json())
middleware را در هر درخواستی که به سرور ما ارسال میشود، اجرا میکند.
API با app.post('/upload_files', uploadFiles)
تنظیم شده است و uploadFiles
کنترلر API میباشد. همانطور که در بالا دیدیم، ما فقط از req.body
خارج میشویم که باید با epxress.json()
پر شود. این موضوع را در مثال زیر آزمایش خواهیم کرد.
در ترمینال خود، node server
را اجرا میکنیم تا سرور راهاندازی شود. اکنون میتوانیم برنامه فرانتاند را در مرورگر خود باز کنیم. هر دو ورودی، name و files را پر کرده سپس روی ارسال کلیک میکنیم. در فرانتاند خود، باید موارد زیر را مشاهده نماییم:
Server started… {}
کد بالا به این معنی است که آبجکت req.body
خالی است که قابل انتظار میباشد. اگر به خاطر داشته باشیم، body-parser
از دادههای multipart
پشتیبانی نمیکند. در عوض، از Multer برای تجزیه فرم استفاده میکنیم.
Multer را با اجرای دستور زیر در ترمینال خود نصب میکنیم:
npm i multer
برای پیکربندی Multer، موارد زیر را به بالای server.js
اضافه مینماییم:
const multer = require("multer"); const upload = multer({ dest: "uploads/" }); ...
اگرچه Multer گزینههای پیکربندی بسیار زیاد دیگری دارد، اما ما فقط از ویژگی dest
برای پروژه خود استفاده میکنیم، که فهرستی را مشخص مینماید که در آن Multer فایلهای کدگذاری شده را ذخیره میکند.
در مرحله بعد، ما از Multer برای رهگیری درخواستهای ورودی در API خود و تجزیه ورودیها برای در دسترس قرار دادن آنها در آبجکت req
استفاده میکنیم:
app.post("/upload_files", upload.array("files"), uploadFiles); function uploadFiles(req, res) { console.log(req.body); console.log(req.files); res.json({ message: "Successfully uploaded files" }); }
برای مدیریت چندین فایل، از upload.array
و برای یک فایل، از upload.single
استفاده میکنیم. باید به این نکته توجه داشته باشیم که آرگومان files
به نام ورودی مشخص شده در formData
بستگی دارد.
Multer ورودیهای متن را به req.body
اضافه میکند و فایلهای ارسال شده را به آرایه req.files
میافزاید. برای مشاهده این مورد در ترمینال، در قسمت فرانتاند پروژه متن را وارد کرده و چندین تصویر را انتخاب میکنیم، سپس بر روی دکمه ارسال کلیک کرده و نتایج ثبت شده را در ترمینال خود بررسی مینماییم.
همانطور که در بالا به آن اشاره کردیم، برای آپلود یک فایل در سرور Node.js خود، از multer.single
استفاده میکنیم. در بلاک کد زیر، کد فرانتاند خود را بهروزرسانی میکنیم تا حاوی فیلدی باشد که به جای چند فایل، فقط یک فایل را قبول کند:
//index.html <div class="input-group"> <label for='single-file'>Select Single file</label> <input id='single-file' type="file"> </div>
فایل script.js
را نیز به صورت زیر بهروزرسانی میکنیم:
const singleFile = document.getElementById("single-file") formData.append("singleFile", singleFile.files[0]) //update the url where we'll send the request to fetch("http://localhost:8000/upload_single", { method: "POST", body: formData, })
در سرور Express، فایل app.js
را بهروزرسانی میکنیم تا آپلودهای تک فایل در مسیر upload_single
را به شرح زیر انجام دهد:
app.post("/upload_single", upload.single("singleFile"), uploadSingle) function uploadSingle(req, res) { console.log("req file", req.file) res.json({ message: "Successfully uploaded single file" }) }
توجه به این نکته لازم است، همانطور که قبلا دیدیم دسترسی به فایلها به جای req.files
با استفاده از req.file
امکانپذیر است، زیرا از تابع upload.single
Multer برای پذیرش یک فایل استفاده میکنیم.
ما ویژگی Multer را برای آپلود یک فایل و آپلود آرایهای از فایلها در یک فیلد مشاهده کردهایم، اما اگر نقطه API endpoint ما به چندین فیلد برای آپلود فایل نیاز داشته باشد، چه فایلهای تکی یا آرایهای، در این صورت چه کاری باید انجام دهیم؟ Multer یک متد upload.fields()
ارائه میکند تا به ما اجازه دهد این سناریوها را مدیریت کنیم.
فایل app.js
خود را به صورت زیر بهروزرسانی میکنیم:
app.post( "/upload_multiple", upload.fields([ { name: "singleFile", maxCount: 1 }, { name: "files", maxCount: 5 }, ]), multipleUploads ) function multipleUploads(req, res) { console.log("singleFile", req.files.singleFile) console.log("files", req.files.files) res.json({ message: "Successfully uploaded Multiple files" }) }
سپس script.js
را در قسمت فرانتاند بهروزرسانی میکنیم تا به endpoint /upload_multiple
درخواست بدهیم:
fetch("http://localhost:8000/upload_multiple", { method: "POST", body: formData, })
req.files.singleFile
حاوی فایل برای فیلد singleFile
است، در حالی که req.files.files
مقدار آپلودهای فیلد فایلها را دارد.
API ما میتواند فایلهای ویدیویی را هم بپذیرد، اما میتوانیم چند فیلتر اضافه کنیم تا مطمئن شویم که فایلهای ویدیویی که دریافت میکنیم، شرایط ما را برآورده میکنند:
const path = require('path'); //... const fileFilter = (req, file, cb) => { const filetypes = /.mp4|.avi|.mkv/ const extname = filetypes.test(path.extname(file.originalname).toLowerCase()) if (extname) { return cb(null, true); } else { cb('Error: Videos Only!'); } }; const upload = multer({ dest: "uploads/", fileFilter, limits: { fileSize: 100000000 }, }) app.post("/upload_video", upload.single("video"), (req, res) => { if (req.file) { console.log("video", video) res.json({ message: "Video uploaded successfully!", }) } else { res.status(400).json({ message: "Failed to upload video" }) } })
سپس، فرانتاند را به صورت زیر بهروزرسانی میکنیم:
//index.html <div class="input-group"> <label for='video'>Select video</label> <input id='video' type="file"> </div> //script.js const video = document.getElementById("video") formData.append("video", video.files[0]) //update the url as follows fetch("http://localhost:8000/upload_video", { method: "POST", body: formData, })
هنگامی که به کاربران اجازه میدهیم تا فایلها را در سرورهای ما آپلود کنند، اغلب باید اندازه آن آپلودها را محدود کنیم تا فضای ذخیرهسازی زیادی اشغال نکنند یا سرعت شبکه را کاهش ندهند. برای محدود کردن حجم فایل، کد زیر را اجرا میکنیم:
const upload = multer({ dest: "uploads/", limits: { fileSize: 100000000 }, //limits file to 100mb })
در ادامه چندین مورد وجود دارد که میتوانیم در صورت عدم تعریف req.file
آنها را انجام دهیم:
upload.array
از upload.single
استفاده میکنیم که باید با استفاده از req.files
به آن دسترسی داشته باشیم.برای ورودیهای متن به تنهایی، آبجکت bodyParser
که در داخل Express استفاده میشود برای تجزیه این ورودیها کافی است. آنها ورودیها را به عنوان یک جفت key-value در آبجکت req.body
در دسترس قرار میدهند. زمانی که فرمها حاوی دادههای multipart
هستند که شامل ورودیهای متن و فایلهایی میباشند که کتابخانه body-parser
نمیتواند از عهده آنها برآید، استفاده از Multer میتواند بسیار مفید باشد.
با استفاده از Multer در Node.js، میتوانیم فایلهای تک یا چندگانه را علاوه بر ورودیهای متنی که از طریق یک فرم ارسال میشوند، مدیریت کنیم. باید به یاد داشته باشیم فقط زمانی که در حال ارسال فایل از طریق فرمها هستیم باید از Multer استفاده کنیم. زیرا، Multer نمیتواند هر فرمی را که multipart نیست مدیریت کند.
در این مقاله، مختصری از روش ارسال فرمها، مزایای body parserها در سرور و نقشی که Multer در مدیریت ورودیهای فرم ایفا میکند، بررسی کردیم. ما همچنین یک برنامه کوچک با استفاده از Node.js و Multer ساختیم تا فرآیند آپلود فایل را بهتر مشاهده کنیم.