在 node.js 中按名称获取 mongodb 集合

Get mongodb collection by name in node.js

我目前正在学习 node.js,这是我参与其中的第一个项目。它(据说)是一个简单的待办事项列表应用程序,其中有多个列表我可以 load/edit/save/remove。

在 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>

看起来像这样:

当我点击列表的 link 时。我尝试使用以下代码加载新列表(这是一个 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("/");
}
});

试试这个,应该有用。

我所做的更改:-

  1. MongoDb connect回调函数改为async.
  2. database.collection(req.params.list_name).find({});
  3. 末尾添加toArray()函数
  4. 并将上述功能制作成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,
    });
  });
});

我不会解决此答案中的 EJS 部分,因为我不合格,而且您提供的代码似乎都不错。但是,我将审查后端部分。

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

总结

从你的第二个代码片段中,有几件事需要讨论:

  1. 异步代码
  2. 数据库连接和概论
  3. 实际实施
  4. 代码构思
  5. [编辑]:Save/Edit 实现

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

异步代码

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

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

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

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 here and here, and see here 上阅读 MongoDB 示例也使用 async/await,但您将在以下部分中看到更多“实际”用法它。

请记住,您使用的内容(无论是回调、承诺还是 async/await 语法)完全取决于您和您的喜好。

数据库连接

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

例如,

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

对于这件事,你有几种选择。一种是使用一个框架来帮助您利用一些代码和样板,例如 Mongoose or work with the native MongoDB driver 我们将这样做,因为您似乎已经使用过它,而且我坚信首先使用最低层将让你更快地学习更高层次的框架。

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

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();

实际实施

当然,这段代码自己不行,需要调用等待,before定义路由。所以,就在 app.route("/:list_name")............ 之前,调用这个:await MongoCli.init();.

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

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;

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

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;

遗言

希望我没有跑题,希望我的回答能帮到你。 另外,我在写答案时看到您现在需要弄清楚如何 post 任务。如果您需要进一步 information/explanation 或什至 posting 任务的帮助,请在评论中告诉我。


编辑(添加):

Save/Edit 实现

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

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