使用 NodeJS GenAI LIVE! 使用 GridFS 和 Multer 将文件上传到 MongoDB!| 2025 年 6 月 4 日

2025-06-10

使用 NodeJS 通过 GridFS 和 Multer 将文件上传到 MongoDB

GenAI LIVE! | 2025年6月4日

您好,在本教程中,我们将学习如何使用GridFS规范将文件直接上传到MongoDB 。

如果您认为 TLDR;只需检查此处的完成代码。

官方文档解释了何时使用此规范上传文件。总结如下:

  • 如果您的文件系统限制目录中的文件数量,则可以使用 GridFS 来存储所需数量的文件。

  • 当您想要访问大文件部分的信息而不必将整个文件加载到内存中时,您可以使用 GridFS 来调用文件的各部分,而无需将整个文件读入内存。

  • 当您希望文件和元数据在多个系统和设施之间自动同步和部署时,可以使用 GridFS。使用地理分布的副本集时,MongoDB 可以自动将文件及其元数据分发到多个 mongod 实例和设施。

由于 GridFS 是以块的形式存储文件的。以下是创建的集合:

  • chunks存储二进制块。
  • 文件存储文件的元数据。

先决条件

  1. NodeJS 长期支持版本
  2. 本地机器上安装的 MongoDB
  3. 代码编辑器

设置本地 NodeJS 服务器

转到命令行,然后输入

npm init -y
Enter fullscreen mode Exit fullscreen mode

这将生成一个具有默认值的 package.json 文件。

然后安装该项目所需的所有依赖项

npm install express mongoose ejs multer multer-gridfs-storage 
Enter fullscreen mode Exit fullscreen mode

在项目根目录中创建一个名为app.js的文件。需要创建服务器所需的包。

const express = require("express");
const app = express();

app.use(express.json());
app.set("view engine", "ejs");

const port = 5001;

app.listen(port, () => {
  console.log("server started on " + port);
});
Enter fullscreen mode Exit fullscreen mode

我们最好创建脚本从命令行运行 Web 应用程序,转到 package.json 文件并在 scripts 键上添加以下内容:

  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js"
  }
Enter fullscreen mode Exit fullscreen mode

然后运行​​npm start,服务器应该在端口 5001 上启动。您应该在命令行上看到一个日志,指出服务器在 5001 上启动

连接数据库、初始化 GridFsStorage 并创建存储

需要所有必要的软件包

const crypto = require("crypto");
const path = require("path");
const mongoose = require("mongoose");
const multer = require("multer");
const GridFsStorage = require("multer-gridfs-storage");
Enter fullscreen mode Exit fullscreen mode

Mongoose 是 MongoDB 的一个 ORM,本教程将使用它。Multer 是一个 NodeJS 中间件,用于简化文件上传。GridFsStorage 是 Multer 的 GridFS 存储引擎,用于将上传的文件直接存储到 MongoDB。Crypto 和 Path 将用于为上传的文件创建唯一的名称。

// DB
const mongoURI = "mongodb://localhost:27017/node-file-upl";

// connection
const conn = mongoose.createConnection(mongoURI, {
  useNewUrlParser: true,
  useUnifiedTopology: true
});
Enter fullscreen mode Exit fullscreen mode

现在,初始化 GridFsStorage

// init gfs
let gfs;
conn.once("open", () => {
  // init stream
  gfs = new mongoose.mongo.GridFSBucket(conn.db, {
    bucketName: "uploads"
  });
});
Enter fullscreen mode Exit fullscreen mode

这里我们使用 mongoose 使用的原生 nodejs-mongodb-drive 并创建一个 GridFSBucket,我们将 db 传递给 bucket,您可以看到我们给出了一个 bucket 名称,这个 bucket 名称将用作集合的名称。

// Storage
const storage = new GridFsStorage({
  url: mongoURI,
  file: (req, file) => {
    return new Promise((resolve, reject) => {
      crypto.randomBytes(16, (err, buf) => {
        if (err) {
          return reject(err);
        }
        const filename = buf.toString("hex") + path.extname(file.originalname);
        const fileInfo = {
          filename: filename,
          bucketName: "uploads"
        };
        resolve(fileInfo);
      });
    });
  }
});

const upload = multer({
  storage
});
Enter fullscreen mode Exit fullscreen mode

现在我们根据 Multer GridFS 初始化存储,并使用加密库中的 randomBytes 方法创建随机字节。

这里我们使用 Promise 构造函数创建一个 Promise,然后将其解析为 fileInfo 对象。此步骤是可选的,因为您只需传递一个 URL 键,存储桶即可正常工作,并且不会更改文件名。例如,您可以像下面这样使用:

const storage = new GridFsStorage({ url : mongoURI})
Enter fullscreen mode Exit fullscreen mode

接下来让我们用模板引擎设置我们的前端并配置 express 来呈现模板。

创建视图

在文件夹根目录下创建一个名为“views”的新文件夹,并在其中创建一个名为“index.ejs”的文件。我们将在这里存储前端视图。我不会费力地讲解 HTML 代码,直接贴出代码就好了。我使用 Bootstrap 进行快速原型设计。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <title>Mongo File Upload</title>
</head>

<body>
    <div class="container">
        <div class="row">
            <div class="col-md-6 m-auto">
                <h1 class="my-4">Lets upload some stuff</h1>
                <form action="/upload" method="post" enctype="multipart/form-data">
                    <div class="custom-file mb-3">
                        <input type="file" class="custom-file-input" name="file" id="file1" onchange="readSingleFile(this.files)">
                        <label class="custom-file-label" for="file1" id="file-label">Choose file</label>
                    </div>
                    <input type="submit" value="Submit" class="btn btn-primary btn-block">
                </form>
            </div>
        </div>
    </div>

    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
    <script>
        function readSingleFile(e) {
            const name = e[0].name;
            document.getElementById("file-label").textContent = name;
        }
    </script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

设置 Express 应用来渲染视图。将视图引擎中间件设置为 ejs

....
app.use(express.json());
app.set("view engine", "ejs");
....

app.get("/", (req, res) => {
 res.render("index")
})
Enter fullscreen mode Exit fullscreen mode

然后再次启动服务器,转到浏览器并打开http://localhost:5001,您应该看到一个使用我们刚刚创建的视图呈现的页面。

替代文本

创建请求来处理表单提交并上传文件

app.post("/upload", upload.single("file"), (req, res) => {
  res.redirect("/");
});
Enter fullscreen mode Exit fullscreen mode

因为我们已经完成了大部分繁重的工作,创建存储桶时,multer 会处理剩下的事情。我们只需要传递中间件,然后重定向到相同的 URL 即可。

棘手的部分是下载或在本例中从 GridFS 存储桶中流式传输数据并呈现图像,为此我们将创建一个用于显示图像的路由,该路由将文件的名称作为参数或作为路由参数传递。

app.get("/image/:filename", (req, res) => {
  // console.log('id', req.params.id)
  const file = gfs
    .find({
      filename: req.params.filename
    })
    .toArray((err, files) => {
      if (!files || files.length === 0) {
        return res.status(404).json({
          err: "no files exist"
        });
      }
      gfs.openDownloadStreamByName(req.params.filename).pipe(res);
    });
});
Enter fullscreen mode Exit fullscreen mode

在 gridfs bucket 上,我们可以访问许多方法,其中之一就是 find,它与 MongoDB 中的常规 find 非常相似,接受文件名作为第一个参数,然后我们将结果转换为数组并检查是否有具有此文件名的文件,如果有,我们使用 gridfs bucket 上的另一个名为openDownloadStreamByName的方法,它再次获取文件名,然后我们使用管道将响应返回给客户端。

到目前为止,我们可以通过上述路由获取图像,但无法在我们的视图上呈现它,所以让我们在呈现 index.ejs 页面的路由内创建一个方法。

....
app.get("/", (req, res) => {
  if(!gfs) {
    console.log("some error occured, check connection to db");
    res.send("some error occured, check connection to db");
    process.exit(0);
  }
  gfs.find().toArray((err, files) => {
    // check if files
    if (!files || files.length === 0) {
      return res.render("index", {
        files: false
      });
    } else {
      const f = files
        .map(file => {
          if (
            file.contentType === "image/png" ||
            file.contentType === "image/jpeg"
          ) {
            file.isImage = true;
          } else {
            file.isImage = false;
          }
          return file;
        })
        .sort((a, b) => {
          return (
            new Date(b["uploadDate"]).getTime() -
            new Date(a["uploadDate"]).getTime()
          );
        });

      return res.render("index", {
        files: f
      });
    }
  });
});
....
Enter fullscreen mode Exit fullscreen mode

在这里您可以看到很多可选代码,例如数组的排序,您可以跳过这些代码。

现在,在模板上,我们循环遍历发送的文件,然后在表单下方显示图片。我们只会渲染 jpg 或 png 类型的文件,该校验可以通过正则表达式进行升级,具体取决于个人偏好。

        <hr>
                <% if(files) { %>
                <% files.forEach(function(file) {%>
                <div class="card mb-3">
                    <div class="card-header">
                        <div class="card-title">
                                <%= file.filename %>
                        </div>
                    </div>
                    <div class="card-body">
                        <% if (file.isImage) { %>
                    <img src="image/<%= file.filename %>" width="250" alt="" class="img-responsive">
                        <%} else { %>
                        <p><% file.filename %></p>
                        <% } %>
                    </div>
                    <div class="card-footer">
                        <form action="/files/del/<%= file._id %>" method="post">
                            <button type="submit" class="btn btn-danger">Remove</button>
                        </form>
                    </div>
                </div>
                <%}) %>
                <% } else { %>
                <p>No files to show</p>
                <% } %>
Enter fullscreen mode Exit fullscreen mode

您可以看到上面的代码中有一个删除按钮,因此让我们创建一个删除路由来从数据库中删除该文件。

替代文本

// files/del/:id
// Delete chunks from the db
app.post("/files/del/:id", (req, res) => {
  gfs.delete(new mongoose.Types.ObjectId(req.params.id), (err, data) => {
    if (err) return res.status(404).json({ err: err.message });
    res.redirect("/");
  });
});
Enter fullscreen mode Exit fullscreen mode

这里我们获取的 id 是一个字符串,因此需要将其转换为 mongodb objectid,然后只有 bucket 方法才能删除对应 id 的文件。为了简单起见,这里我没有使用 delete HTTP 方法,您可以随意使用它,如果愿意的话,post 请求在这里就很好用。

结论

我们可以看到,MongoDB 提供了一个很好的解决方案来在数据库中存储文件,并且在创建存储空间较少的 WebApp 时非常方便,但请记住,您只能存储最大 16mb 的文档。

如果该帖子对您有帮助,请点赞并为该仓库加注星标。

替代文本

鏂囩珷鏉ユ簮锛�https://dev.to/shubhambattoo/uploading-files-to-mongodb-with-gridfs-and-multer-using-nodejs-5aed
PREV
使用 SVG 的更好方法 GenAI LIVE!| 2025 年 6 月 4 日
NEXT
帮助 Web 开发人员保持更新的有用免费和付费资源