在Node.js应用程序中使用Formidable上传文件
每当我们在任何网站的客户端提交一个表单时,所有的表单数据都会到服务器端。通常情况下,表单数据在我们提交给服务器之前会被编码。我们可以通过在HTML的<form>
标签中指定enctype属性来做到这一点。
如果我们不指定它,表单数据会被编码为默认类型。
简介
当我们处理纯文本数据如姓名、电子邮件、密码等时,通常会出现这种情况。但如果我们上传的是某种文件,我们需要指定enctype属性的值为 "multipart/form-data"。当我们使用有文件上传控件的表单时,我们需要这个值。
在这篇文章中,我们将学习如何使用node.js、express和formidable来上传文件。Formidable是一个Node.js模块,用于解析表单数据,特别是文件上传。
前提条件
- 对Node.js和NPM有良好的理解。
- MongoDB必须安装在你的设备中,你需要对数据库有一个基本的了解。
- 对命令行或代码编辑器中的集成终端有良好的理解。
本教程的目标
- 如何设计一个用于发布多部分表单数据的API端点。
- 如何使用Formidable来处理文件的上传。
- 如何在服务器端管理和存储这些文件。
- 如何在前台查看这些文件。
然而,由于这些主题有一半已经在我之前的文章中涵盖了,因此我将在需要的地方参考那篇文章。
项目设置
这些步骤基本上涉及以下内容。
- 文件夹结构
- 用Mongoose设置MongoDB和
- 渲染HTML文件
另外,供参考。你的app.js文件和文件夹结构应该是这样的。
文件夹结构。
├───model (folder) <br/>
│ └───fileSchema.js (file) <br/>
├───node_modules (folder) <br/>
├───public (folder) <br/>
│ └───css (folder), files(folder) <br/>
|──────└───style.css (file) <br/>
|───views (folder) <br/>
│────└───index.ejs (file) <br/>
├───app.js (file) <br/>
├───package-lock.json (file) <br/>
├───package.json (file) <br/>
├───server.js (file) <br/>
Formidable
如前所述,formidable
是一个Node.js模块,用于解析表单数据,特别是文件上传。
让我们从安装formidable开始。
在你的终端写下这个命令。
npm install formidable
安装包后,我们将在app.js文件的顶部导入它。
const formidable = require("formidable");
然后我们将创建一个API端点来上传文件。
注意:确保用于渲染页面的端点在所有API端点的最后。
// API Endpoint for uploading file
app.post("/api/uploadFile", (req, res) => {
// Stuff to be added soon
});
让我们开始使用formidable
这个包的工作原理是创建一个来自客户端的表单数据的实例。它将是一个带有一些默认键值对的对象实例,我们可以按照我们的要求进行修改。
让我们来看看它。
在上面的API回调函数中添加这段代码。
const form = formidable.IncomingForm();
console.log(form);
我们可以通过在这个API上发送带文件的请求来查看表单数据。但是,在此之前,我们需要在我们的HTML代码中,即在index.ejs
文件中做一些小改动。
将表单中的action属性的值改为/api/uploadFile
。
<form action="/api/uploadFile" enctype="multipart/form-data" method="POST">
<input type="file" class="admin__input" id="myFile" name="myFile" />
<input class="admin__submit" type="submit" />
</form>
如果你在重启服务器后从你渲染的网页中上传一些东西,你应该在你的终端看到这样的东西。
IncomingForm {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
error: null,
ended: false,
maxFields: 1000,
maxFieldsSize: 20971520,
maxFileSize: 209715200,
keepExtensions: false,
uploadDir: 'C:\\Users\\SARTHA~1\\AppData\\Local\\Temp',
encoding: 'utf-8',
headers: null,
type: null,
hash: false,
multiples: false,
bytesReceived: null,
bytesExpected: null,
_parser: null,
_flushing: 0,
_fieldsSize: 0,
_fileSize: 0,
openedFiles: [],
[Symbol(kCapture)]: false
}
正如你所看到的,我们在这个对象中有许多属性,包括encoding
,maxFileSize
等。通过这些属性,我们可以在我们的规范中配置formidable。
我们必须创建一个变量,指向我们想要存储文件的目录/文件夹。要做到这一点,在创建表单实例后添加以下代码。
const uploadFolder = path.join(__dirname, "public", "files");
现在,我们的uploadFolder
变量指向public
目录下的文件夹,该目录存在于我们项目的根级别。
现在让我们通过改变表单实例中的几个属性来改变配置。
// Basic Configuration
form.multiples = true;
form.maxFileSize = 50 * 1024 * 1024; // 5MB
form.uploadDir = uploadFolder;
console.log(form);
-
multiples
属性默认设置为false,正如我们在控制台记录的表单对象中看到的那样。我们可以把它设置为 "true",这样用户就可以一次添加多个文件。另外,我们必须在HTML中的输入标签中添加多个属性,以确保用户能够上传多个文件。 -
我们可以限制用户上传的文件的大小。默认是200MB,但我们将其限制为5MB。
-
最后但并非最不重要的是,我们必须将上传目录改为我们创建的目录。
你还可以改变许多其他的属性,但这对大多数的使用情况来说已经足够了。
如果我们在控制台中再记录一次表单实例,我们可以看到上述属性的不同。
IncomingForm {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
error: null,
ended: false,
maxFields: 1000,
maxFieldsSize: 20971520,
maxFileSize: 52428800,
keepExtensions: false,
uploadDir: 'D:\\Sarthak\\upload-multipart-form-data-using-formidable\\public\\files',
encoding: 'utf-8',
headers: null,
type: null,
hash: false,
multiples: true,
bytesReceived: null,
bytesExpected: null,
_parser: null,
_flushing: 0,
_fieldsSize: 0,
_fileSize: 0,
openedFiles: [],
[Symbol(kCapture)]: false
}
由于我们已经完成了基本配置,我们可以开始解析我们的文件。要做到这一点,我们使用一个内置的解析函数,我们可以在我们的表单实例上调用。
在配置下面添加这段代码。
// Parsing
form.parse(req, async (err, fields, files) => {
console.log(fields);
console.log(files);
if (err) {
console.log("Error parsing the files");
return res.status(400).json({
status: "Fail",
message: "There was an error parsing the files",
error: err,
});
}
});
.parse(request, callback)
"parse "函数解析一个包含表单数据的传入的Node.js请求。如果提供了一个回调,所有的字段和文件都会被收集并传递给回调。
我们的目的是根据我们自己的需要来解析和存储这些文件,因此我们需要在工作之前看一下这些文件。因此,我们有两个日志语句来看看我们在回调函数中得到的数据。
另外,我们将在第一步就处理任何可能出现的错误。这是因为我们不希望解析任何有一些潜在错误的文件。我们通过检查是否有任何错误来做到这一点。如果我们确实遇到了任何错误,我们可以发送状态代码为400的响应,描绘出一个错误的请求。
如果你通过提交文件看到这个代码,你会在控制台看到解析过的表单数据的日志。注意,在你的public下的files文件夹中已经创建了一个新文件。但是,该文件将是不可读的,因为该文件还没有扩展名。
你记录的数据应该是这样的。
{}
{
myFile: File {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
size: 735154,
path: 'D:\\Sarthak\\upload-multipart-form-data-using-multer\\public\\files\\upload_d235df36a536ff3bc3cbfa8ac0f86e2f',
name: 'College ID.pdf',
type: 'application/pdf',
hash: null,
lastModifiedDate: 2021-05-30T12:40:05.872Z,
_writeStream: WriteStream {
_writableState: [WritableState],
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
path: 'D:\\Sarthak\\upload-multipart-form-data-using-multer\\public\\files\\upload_d235df36a536ff3bc3cbfa8ac0f86e2f',
fd: 4,
flags: 'w',
mode: 438,
start: undefined,
autoClose: true,
pos: undefined,
bytesWritten: 735154,
closed: false,
[Symbol(kFs)]: [Object],
[Symbol(kCapture)]: false,
[Symbol(kIsPerformingIO)]: false
},
[Symbol(kCapture)]: false
}
}
fields
对象是空的,我们不想要它。在文件对象中,我们可以看到我们在HTML中提到的输入标签的名称(myFile
)。我们甚至可以获得带有原始扩展名的文件名。所有这些信息将帮助我们更精确地保存和管理我们的文件。
在继续前进之前,我们必须看一下一个特殊情况。由于用户可以同时上传多个文件,传入的解析数据将是一个对象的数组。因此,在进一步工作之前,我们必须每次检查我们得到的是多个文件还是单个文件。
有多个文件的数据看起来与此类似。
{
myFile: [
File {....},
File {....}
]
}
接下来的步骤。
- 我们将分别处理单个文件和多个文件。
- 在这两种情况下,我们将通过创建一个单独的函数来检查文件是否有效。
- 如果文件无效,那么我们将抛出一个错误。如果它是有效的,我们将重命名它并将文件名存储在我们的数据库中。
现在,添加这段代码,然后我们将走完每个步骤。
// Check if multiple files or a single file
if (!files.myFile.length) {
//Single file
const file = files.myFile;
// checks if the file is valid
const isValid = isFileValid(file);
// creates a valid name by removing spaces
const fileName = encodeURIComponent(file.name.replace(/\s/g, "-"));
if (!isValid) {
// throes error if file isn't valid
return res.status(400).json({
status: "Fail",
message: "The file type is not a valid type",
});
}
try {
// renames the file in the directory
fs.renameSync(file.path, join(uploadFolder, fileName));
} catch (error) {
console.log(error);
}
try {
// stores the fileName in the database
const newFile = await File.create({
name: `files/${fileName}`,
});
return res.status(200).json({
status: "success",
message: "File created successfully!!",
});
} catch (error) {
res.json({
error,
});
}
} else {
// Multiple files
}
-
在第一步,我们要检查用户是否上传了多个文件。我们通过检查文件解析数据中myFile属性的长度来做到这一点。如果长度为零,那么就意味着只有一个文件被上传。
-
下一步是看一下上传的文件是否有效。我们通过创建一个特殊的函数来做到这一点,该函数是这样的。
const isFileValid = (file) => {
const type = file.type.split("/").pop();
const validTypes = ["jpg", "jpeg", "png", "pdf"];
if (validTypes.indexOf(type) === -1) {
return false;
}
return true;
};
在这个函数中,我们要提取上传文件的原始扩展名。如果它存在于我们描述的有效扩展名数组中,那么我们可以返回真,否则我们返回假。
-
我们正在创建一个有效的文件名,用 "破折号 "去除所有的 "空格"。这是通过正则表达式和
encodeURIComponent()
函数的帮助完成的。 -
如果文件是无效的,我们将抛出一个错误。如果它是有效的,我们将在Node.js的核心模块
fs
的帮助下,在我们的文件目录中重命名该文件。
不要忘了在你文件的顶部导入fs
模块。
之后,我们可以将文件的名称存储在本教程前面托管的MongoDB云数据库中。
试着自己完成多个文件的else块!这里是整个上传函数,供参考。
有了这个,如果你将尝试上传一个具有有效扩展名的文件或图片。它将以我们定义的名称保存在你的文件目录中。同时,文件名会被储存在你的云端数据库中,这样你就可以在我们的前端访问它。
在前端查看这些文件
Formidable vs Multer
我之前的文章是关于Multer的,这篇文章是关于Formidable的。两者都是npm包,但一个作为模块,一个作为中间件。
我发现formidable
工作起来更简单,因为配置multer
要复杂得多。但是,在一些使用实例中,你必须与Multer合作。
例如,如果你选择在将图片保存在你的服务器上之前调整它们的大小,那么Multer提供了一个被称为Buffer storage
的东西,可以帮助你。
因此,这在很大程度上取决于你的使用情况和你觉得什么更容易使用。