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 ساختیم تا فرآیند آپلود فایل را بهتر مشاهده کنیم.