MongoDB 學習筆記 – 用 Node.js 執行 CRUD

MongoDB 學習筆記 - 用 Node.js 執行 CRUD
MongoDB 學習筆記 - 用 Node.js 執行 CRUD

本篇要解決的問題

前二篇我們是用 MongoDB Shell,用命令的方式來對資料庫進行 CRUD(增刪查改),這篇是用 Node.js 的方式,寫程式碼來進行 CRUD。

寫這篇筆記文時,發現 MongoDB Shell 如果很熟,在 Node.js 上做 CRUD 是很容易上手的,因為方式上很多相同的地方,建議可以先看上二篇的筆記文後,再來看這篇:ReadDelete、Update


連結資料庫

在執行增刪查改資料庫之前,我們要先用 Node.js 來連結 MongoDB。

假設我們的資料庫是「bookstore」,bookstore 下有一個 collection 叫「books」。

我們寫的 Node.js,使用 express 來架 server,所以在開始前,會先執行以下命令安裝需要的 package:

$ npm init -y
$ npm install express
$ npm install mongodb

我們把連結資料庫的部份,另存一個 db.js,方便管理。

db.js:

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

let dbConnection;

const connectToDb = async () => {
  try {
    const dbUri = 'mongodb://localhost:27017/';
    const collectionName = 'bookstore';
    const client = await MongoClient.connect(dbUri);
    dbConnection = client.db(collectionName);
    console.log('Successfully connected to MongoDB.');
  } catch (err) {
    console.error('Failed to connect to MongoDB', err);
    process.exit(1);
  }
};

const getDb = () => {
  if (!dbConnection) {
    throw new Error('No database connection.');
  }
  return dbConnection;
};

module.exports = { connectToDb, getDb };

比較安全的作法,是將 MongoDB 的連接字符串存在 .env,這邊為了示範程式碼,就直接寫出來。

接著我們主要的 app.js,就可以引用 express、db.js,來連結資料庫:

app.js:

const express = require('express');
const { ObjectId } = require('mongodb');
const { connectToDb, getDb } = require('./db');

const app = express();
app.use(express.json());

// connect to the database
let db;

(async () => {
  try {
    await connectToDb();
    app.listen(3000, () => {
      console.log('Server is running on port 3000');
      db = getDb();
    });
  } catch (err) {
    console.error('Unable to start the server', err);
    process.exit(1);
  }
})();

上面這段執行後,後續就可以用 db.collection() 來對資料庫做增刪查改。

而以下二行,是後面會用到的,先 require 進來:

const { ObjectId } = require('mongodb');
app.use(express.json());

Read 查詢資料庫

使用 find + limit + toArray

就跟 MongoDB Shell 一樣,用 find() 查資料。

toArray() 會將所有資料轉成陣列。

要注意的是,MongoDB Shell 預設只會給前 20 筆資料,因此即使資料量有幾千萬筆,也不怕一個命令下去,記憶體就崩了。

但 Node.js 執行 find() 時,是沒有這個預設的,所以記得要用 limit() 限制資料量。

app.get('/findLimit', (req, res) => {
  db.collection('books')
    .find()
    .limit(5)
    .toArray()
    .then(books => res.status(200).json(books))
    .catch(err => {
      res.status(500).send('Error occurred');
    });
});

使用 find + forEach

forEach() 一次處理一筆資料,代表只有當前被處理的文檔佔用記憶體,從而避免了將大量數據一次性加載到記憶體中可能導致的壓力。

toArray() 不同的地方就在於,forEach() 不會將游標(Cursor)中的所有文檔轉換成一個陣列。

如果資料量很大,建議使用 forEach,可以減少應用程序的記憶體使用量。

app.get('/forEach', (req, res) => {
  const books = [];
  db.collection('books')
    .find()
    .sort({ author: 1 })
    .forEach(book => books.push(book))
    .then(() => res.status(200).json(books))
    .catch(err => {
        res.status(500).send('Error occurred');
    });
});

find + project,取出的資料,只回傳特定字段

如果我們的 document 字段很多,而我們只需要其中的幾個字段,比方一本書的資料,字段可能有書名、出版社、出版日期、作者、譯者、頁數、分類……,我們在清單頁上不會全部顯示,可能只需要顯示書名、作者這二個字段的話,就用 project()

app.get('/projection', (req, res) => {
  db.collection('books')
    .find()
    .limit(3)
    .project({ _id: 0, title: 1, author: 1 })
    .toArray()
    .then(books => res.status(200).json(books))
    .catch(err => {
      res.status(500).send('Error occurred');
    });
});

上面程式碼中的 projec(),可以改寫成:

app.get('/projection2', (req, res) => {
  db.collection('books')
    .find({}, { projection: { _id: 0, title: 1, author: 1 } })
    .limit(3)
    .toArray()
    .then(books => res.status(200).json(books))
    .catch(err => {
      res.status(500).send('Error occurred');
    });
});

第一種方式比較靈活,因為後面還可以再加上其它運算子,實際使用就看個人習慣。

skip 分頁功能

我們實際維運一個站時,比方網路書店,資料會有幾千幾萬筆,不可能一個頁面就全部顯示出來,後端也不會做一次取全部 collection 內的 documents 這種事。

因此,分頁功能是很重要的,在 MonogoDB 中主要使用 skip() 來執行分頁。

app.get('/pagination', (req, res) => {

  const page = parseInt(req.query.page) || 1;
  const size = parseInt(req.query.size) || 10;

  const books = [];

  db.collection('books')
    .find()
    .skip((page - 1) * size)
    .limit(size)
    .forEach(book => books.push(book))
    .then(() => res.status(200).json(books))
    .catch(err => {
      res.status(500).send('Error occurred');
    });
});

上面的程式碼中,我們主要讀取網址上的 page、size 這二個參數,page 是第幾頁,size 是指一頁有幾筆。

例如,網址為 /books?page=2&size=5,代表第 2 頁,一頁顯示 5 筆資料。

findOne,查詢單筆資料

清單頁上用 find(),因為一頁顯示需要多筆資料,而進到單頁後,只需要取一筆詳細資料就行。

只查詢單筆資料用 findOne()

app.get('/book/:id', (req, res) => {

  const id = req.params.id;

  // 檢查 id 是否為有效的 ObjectId
  if (ObjectId.isValid(id)) {
    db.collection('books')
      .findOne({ _id: new ObjectId(id) })
      .then(book => res.status(200).json(book))
      .catch(err => {
        res.status(500).send('Error occurred');
      });
  } else {
    res.status(400).send('Invalid ID');
  }

});

我們使用網址上的第二段來當作查詢的 ID。

「檢查 id 是否為有效的 ObjectId」這個判斷式很重要,可以避免使用者亂輸入網址,或是網址已不存在時,會看見頁面錯誤。加上了判斷,就可以在遇到不存在的 ID 時,執行我們的設計,比方直接轉去 404 頁。


insert 新增資料

如果想新增單筆資料,用 insertOne()

app.post('/createBook', (req, res) => {
  const book = req.body;
  db.collection('books')
    .insertOne(book)
    .then(result => res.status(201).json(result))
    .catch(err => {
      res.status(500).send('Error occurred');
    });
});

如果想一次新增多筆資料,用 insertMany()

app.post('/createBooks', (req, res) => {
  const books = req.body;
  db.collection('books')
    .insertMany(books)
    .then(result => res.status(201).json(result))
    .catch(err => {
      res.status(500).send('Error occurred');
    });
});

updateOne 更新資料

app.patch('/updateBook/:id', (req, res) => {

  const id = req.params.id;
  const book = req.body;

  if (ObjectId.isValid(id)) {
    db.collection('books')
      .updateOne({ _id: new ObjectId(id) }, { $set: book })
      .then(result => res.status(200).json(result))
      .catch(err => {
        res.status(500).send('Error occurred');
      });
  } else {
    res.status(400).send('Invalid ID');
  }

});

deleteOne 刪除資料

app.delete('/deleteBook/:id', (req, res) => {

  const id = req.params.id;

  if (ObjectId.isValid(id)) {
    db.collection('books')
      .deleteOne({ _id: new ObjectId(id) })
      .then(result => res.status(200).json(result))
      .catch(err => {
        res.status(500).send('Error occurred');
      });
  } else {
    res.status(400).send('Invalid ID');
  }

});
Summary
MongoDB 學習筆記 - 用 Node.js 執行 CRUD
Article Name
MongoDB 學習筆記 - 用 Node.js 執行 CRUD
Description
本筆記文解析如何利用 Node.js 實作 MongoDB 的 CRUD 操作。涵蓋連線設定、數據處理到優化查詢。
August
Let's Write
Let's Write
https://letswritetw.github.io/letswritetw/dist/img/logo_512.png
訂閱
通知
guest

0 Comments
最舊
最新
Inline Feedbacks
看所有留言