【问题标题】:Get mongodb collection by name in node.js在 node.js 中按名称获取 mongodb 集合
【发布时间】:2022-01-04 16:21:43
【问题描述】:

我目前正在学习 node.js,这是我在其中的第一个项目。它(据说)是一个简单的待办事项列表应用程序,我可以在其中加载/编辑/保存/删除多个列表。

在 todo_list.ejs 文件中,我有一个 div,其中列出了所有集合名称:

    <div id="list_container" class="lists">
        <ul id="col_list" class="collection-list">
            <% lists.forEach(list => { %>
                <li class="collection-list-item">
                    <div class="list-name-container">                        
                        <a href="/<%=list.name %>" class="list-link">
                            <span class="list-name" name="list_name"><%=list.name %></span>
                        </a>
                    </div>
                </li>
            <% }) %>
        </ul>
    </div>

看起来像这样:

当我单击列表的链接时。我尝试使用以下代码加载新列表(这是一个 mongodb 集合):

app.route("/:list_name").get((req, res) => {
MongoClient.connect(process.env.DB_CONNECT, (err, db) => {
    if(err) throw err;
    var database = db.db("myFirstDatabase");
    const cursor = database.collection(req.params.list_name).find({}); /* stuck here */
    database.listCollections().toArray((err, collections) => {
        if(err) throw err;
        db.close();
        collections.forEach(element => {
            if(element.name == req.params.list_name){
                current_list = element;
                current_list_name = element.name;
            }
        });
        task.find({}, (err, todo_tasks) => { /*currently using the model.find() method to list all the documents which always looks at the "tasks" collection*/
            res.render("todo_list.ejs", { tasks: todo_tasks, lists: collections, curr_list: current_list_name });
        });
    });
 });
 });

我评论了我在上面的代码中遇到的问题。我正在尝试按名称获取 mongodb 集合,然后将其所有内容加载到列表中,但我不知道如何按名称查找集合。阅读 node.js 文档将我带到光标对象,它有大量的信息和属性,我不知道如何处理......

有没有一种简单的方法可以按名称查找集合并获取其文档列表?

编辑 1:

这是我添加任务的地方:

//ADD TASK TO LIST
app.post('/', async (req, res) => {
const tsk = new task({ /*the mongodb model for tasks*/
    content: req.body.content,
    deadline: req.body.deadline
});

try {
    await tsk.save();
    res.redirect("/");
} catch(e) {
    console.log(e);
    res.redirect("/");
}
});

【问题讨论】:

  • 您的代码中有很多错误的地方(不是想烤什么的,我自己也去过那里,迷失在 JS 世界中)。如果您需要详细的答案(我稍后会发布),请在此处给我留言,如果没有,请尝试以下任何其他答案,看看它们是否适合您。
  • @GaëtanBoyals 无论如何,如果我的代码在第一个项目中表现良好,我会感到惊讶。我只是想学习。
  • 我会认为这是肯定的。我会留下一个详细的答案来解决我在您的代码摘录中看到的内容。
  • 非常感谢您,任何和所有信息都有帮助

标签: node.js mongodb ejs


【解决方案1】:

我不会在此答案中解决 EJS 部分,因为我不合格,而且您提供的代码似乎都很好。不过,我会回顾一下后端部分。

另外,由于我不知道你有什么样的编码背景(如果有的话),这个答案将包含很多关于可能简单概念的解释。

总结

从您的第二个代码 sn-p 中,有几件事需要讨论:

  1. 异步代码
  2. 数据库连接和一般性
  3. 实际实现
  4. 代码概念
  5. [编辑]:保存/编辑实现

根据 OP 的知识,还有很多内容需要涵盖,例如 try/catch 子句、MongoDB 模型验证、express 的 Router 的使用等等,但我只会在需要时编辑我的答案。

异步代码

对于其余的答案,大部分代码将被async/await 关键字包围。这些是代码正常工作所必需的。

基本上,JS 是一种为 Web 设计的语言,您有时需要等待网络或数据库请求完成,然后再执行任何其他操作。这就是 callbackspromisesasync/await 语法(这是 promise 的语法糖)派上用场的地方。

假设您需要像您的示例一样检索任务列表:

app.route("/:list_name").get((req, res) => {
MongoClient.connect(process.env.DB_CONNECT, (err, db) => {
    if(err) throw err;
    var database = db.db("myFirstDatabase");
    const cursor = database.collection(req.params.list_name).find({}); /* stuck here */
    console.log(cursor);
    // ..........
  });
});

JS 默认是异步的,如果你运行这段代码,cursor 很有可能是undefined。原因是代码没有等待 database.collection(............. 完成以继续执行。但是在前面提到的callback/promises/async-await的帮助下,我们的代码现在可以等到这条指令完成了。

您可以阅读 async/await herehere,并查看 here MongoDB 示例也在使用 async/await,但您将在以下部分中看到它的更多“实际”用法。

请记住,您正在使用什么(无论是回调、承诺还是异步/等待语法)完全取决于您和您的偏好。

数据库连接

由于当前编写的代码,每次用户单击列表中的任何项目时,都会建立与 MongoDB 的连接,并且该连接不属于路由处理程序。您的后端应用程序应该连接到数据库一次(至少在这种情况下,对于某些高级情况启动多个连接可能很有用),并在您的后端应用程序停止时关闭连接(通常不是这种情况) API)。

Atlas 云数据库例如,限制为 500 个连接。这意味着,假设有 501 个用户同时点击您前端列表中的某个项目,最好的情况是有人没有得到他所要求的内容,但情况可能更糟。

对于这个问题,你有几个选择。一种是使用一个可以帮助您利用一些代码和样板的框架,例如Mongoose 或使用我们将做的native MongoDB driver,因为您似乎已经使用它并且我坚信使用首先最低层将使您更快地学习更高级别的框架。

现在,让我们解决这个问题。我们希望将数据库连接放在其他会被调用一次的地方。同样,您可以使用几个选项,但我喜欢为它创建一个类,并导出一个新实例以在我的代码中的任何位置执行我想要的操作。这是一个(非常)简单的示例,说明了我的最小选择:

mongo-client.js:

const { MongoClient } = require('mongodb');

class MongoCli {
  constructor() {
    let url = `mongodb://testuser:my_sup3r_passwOrd@127.0.0.1:27017/?authSource=my_database_name`;
    this.client = new MongoClient(url, { useUnifiedTopology: true });
  }

  async init() {
    if (this.client) {
      await this.client.connect();
      this.db = this.client.db('test');
    } else
      console.warn("Client is not initialized properly");
  }
}

module.exports = new MongoCli();

实际实现

当然,这个代码本身是行不通的,我们需要调用并等待它,定义路由之前。所以,就在app.route("/:list_name")............ 之前,调用这个:await MongoCli.init();

这是我(再次,真的)简单的server.js 的样子(我已将 mongo-client 代码与服务器分离):

const express = require('express');
const MongoCli = require('./mongo-cli.js');

const server = async () => {
  const app = express();

  await MongoCli.init();

  app.route("/:list_name").get(async (req, res) => {
    
  });
  return app;
};

module.exports = server;

现在,让我们从头开始实现你真正想要的东西,即一旦用户点击任务主题,它将显示他点击的主题的所有任务:

const express = require('express');
const MongoCli = require('./mongo-cli.js');

const server = async () => {
  const app = express();

  await MongoCli.init();

  app.route("/:list_name").get(async (req, res) => {
    // we will query the collection specified by req.params.list_name
    // then, .find({}) indicates we want all the results (empty filter)
    // finally, we call .toArray() to transform a Cursor to a human-readable array
    const tasks = await MongoCli.db.collection(req.params.list_name).find({}).toArray();
    // making sure we got what we needed, you can remove the line below
    console.log(tasks);
    // return a HTTP 200 status code, along with the results we just queried
    res.status(200).json(tasks);
  });
  return app;
};

module.exports = server;

很简单,对吧? 请记住,我的server.js 可能看起来不像你的,因为有很多方法可以处理这个问题,开发人员可以找到自己喜欢的方法,但你明白了。

代码概念

我们的 GET 路由开始运行,当我们调用路由时我们得到了结果,一切都很好! ...不完全是。

如果我们有 1500 个任务主题,现在会发生什么?我们真的应该创建 1500 个不同的集合,知道任务由描述、状态、截止日期,最终是一个名称组成吗?当然,我们可以做到,但这并不意味着我们必须这样做。

相反,如何创建一个且唯一的集合 tasks,并向其中添加一个密钥 topic

考虑到上面的句子,现在的路线如下所示:

const express = require('express');
const MongoCli = require('./mongo-cli.js');

const server = async () => {
  const app = express();

  await MongoCli.init();

  app.route("/:topic_wanted").get(async (req, res) => {
    // we now know the collection is named 'tasks'
    // then, .find({topic: req.params.topic_wanted}) indicates we want all the results where the key 'topic' corresponds to req.params.topic_wanted
    // finally, we call .toArray() to transform a Cursor to a human-readable array
    const tasks = await MongoCli.db.collection('tasks').find({topic: req.params.topic_wanted}).toArray();
    // making sure we got what we needed
    console.log(tasks);
    // return a HTTP 200 OK, along with the results we just queried
    res.status(200).json(tasks);
  });
  return app;
};

module.exports = server;

遗言

我希望我不是太离题,我的回答可以帮助你。 另外,我在写答案时看到您现在需要弄清楚如何发布任务。如果您需要更多信息/解释甚至发布任务的帮助,请在 cmets 中告诉我。


编辑(添加):

保存/编辑实现

看到您创建新任务的实现,我假设您已经使用mongoose。不幸的是,在 Mongoose 中声明模型时,它会自动搜索(如果不存在,则创建)与声明的模型具有相同名称的集合,但小写和复数形式除外(see here 了解更多信息)。这意味着您不能声明 new task 并将其分配给名为“users”的集合。

这就是这个答案的第 4 部分“代码概念”发挥作用的地方。否则,您编辑的代码没有“重大”缺陷。

【讨论】:

  • 如果您可以麻烦解释如何保存/编辑导入的列表,我会很高兴。目前,当我添加一个任务时,它总是添加到名为“tasks”的(我猜是默认的)集合中。如有必要,我会将添加任务的部分添加到列表中。
  • 没问题!确实,我希望您在添加任务的部分编辑您的帖子,以便我可以看到需要更改的内容。
  • 已添加。如果需要更多信息,请告诉我
  • 谢谢!也编辑了我的答案!
  • 是的,这段代码使用了猫鼬模型。所有操作都由它完成(添加/编辑/删除任务)。我想我会在你的回答的帮助下完成并改变它。
【解决方案2】:

试试这个,应该可以的。

我所做的更改:-

  1. MongoDb connect 回调函数更改为async
  2. database.collection(req.params.list_name).find({});末尾添加toArray()函数
  3. 并将上面的函数做成await

你可以选择.thenasync/await,由你决定!

app.route("/:list_name").get((req, res) => {
MongoClient.connect(process.env.DB_CONNECT,async (err, db) => {
    if(err) throw err;
    var database = db.db("myFirstDatabase");
    const todo_tasks = await database.collection(req.params.list_name).find({}).toArray(); /* add '.toArray()' */
    database.listCollections().toArray((err, collections) => {
        if(err) throw err;
        db.close();
        collections.forEach(element => {
            if(element.name == req.params.list_name){
                current_list = element;
                current_list_name = element.name;
            }
        });
        res.render("todo_list.ejs", { tasks: todo_tasks, lists: collections, curr_list: current_list_name });
      });
    });
 });

经过一些改进:-

app.route("/:list_name").get((req, res) => {
  // Connecting to MongoDb database
  MongoClient.connect(process.env.DB_CONNECT, async (err, db) => {
    if (err) throw err;
    // Choosing 'myFirstDatabase' database
    const database = db.db("myFirstDatabase");

    let todo_tasks = [];
    let collections = [];
    let current_list_name = "";

    // Getting selected list items(todo tasks) to array
    try {
      todo_tasks = await database.collection(req.params.list_name).find({}).toArray(); // Change :- Add '.toArray()'
    } catch (err) {
      if (err) throw err;
    }

    // Getting collections names
    try {
      collections = await database.listCollections().toArray();
      db.close();
    } catch (err) {
      if (err) throw err;
    }

    // Getting selected list details
    collections.forEach(element => {
      if (element.name === req.params.list_name) {
        current_list = element; // I don't understand what this code here
        current_list_name = element.name;
      }
    });

    // Rendering front end
    res.render("todo_list.ejs", {
      tasks: todo_tasks,
      lists: collections,
      curr_list: current_list_name,
    });
  });
});

【讨论】:

  • 它确实有效,谢谢。现在我只需要弄清楚如何将任务实际添加到加载的列表中。
  • 如果可行,则将此答案标记为已接受的答案,请参阅:- stackoverflow.com/help/someone-answers
  • 还要检查我添加到答案中的代码的改进版本。
  • 我会先等着看上面的详细答案会是什么样子,如果它没有比你的答案更有帮助,我会接受。 current_list 也是我尝试获取集合及其内容的一种方式,我只是忘了删除它..
  • 那么你可以投票给答案,如果代码对你有用或对你有帮助,我应该得到这个答案。
猜你喜欢
  • 1970-01-01
  • 2013-08-29
  • 1970-01-01
  • 1970-01-01
  • 2012-06-14
  • 2020-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多