Node.jsでMongoDB Atlasを利用する(Node.js その4)

「MongoDB」は「NoSQL」のデータベースです
「NoSQL」は、データを柔軟に管理でき、大量のデータを扱うサービスでも比較的高速に処理できます

MongoDBサーバー等をローカルPCにインストールしたくなかったので^^;
MongoDB Atlasを試してみようと思います😊

目次
  1. MongoDB Atlas
  2. Node.jsから接続準備
  3. MonogoDBの構造と接続
  4. ドキュメントのCRUD操作
  5. Mongooseを試してみる

MongoDB Atlas

「MongoDB Atlas」は、MongoDB社が提供MongoDB CloudのMongoDBフルマネージドサービスです
無料枠(0.5GBのストレージ)があります
余談:mLabというMongoDB用クラウドサービスもありますが、MongoDBに買収されて「MongoDB Atlas」に統合されるようです

クラスター(データベース)に接続するために必要なこと

  1. Atlasアカウントに登録 https://www.mongodb.com/cloud/atlas/register
  2. 無料のクラスター(データベース)をデプロイします(ClusterTierとAdditionalSettingは変更すると無料枠で設定できないのでそのままにして、ClusterNameを設定し「Create Cluster」をクリックします)
    *デフォルトでクラスター(データベース)のデプロイ先をAWSなどの特定のリージョンに設定されます
  3. 接続IPアドレスをIPアクセスリストに追加(「Add Current IP Address」をクリック)
  4. クラスター(データベース)のユーザーを作成(接続時に、ここで設定するUsernameとPasswordを使用します)

クラスター(データベース)に接続する方法の選択肢
*今回はアプリケーションから操作するを選択して、Node.jsから接続します

  • mongo shellをインストールしてシェルで操作
  • CompassをインストールしてGUIツールで操作
  • アプリケーションから操作する(Node.jsにmongodbドライバーをインストール)

Atlasは、Organization > Project > Clusterというような階層で管理されています
OrganizationとProjectはAtlas固有の単位で、Atlasのアカウントを作ると適当なデフォルト名のOrganizationとProjectが作成されます
Organizationは支払いの単位で、Projectは、MongoDBユーザー定義やセキュリティ設定などをまとめる単位です
無料のClusterTier(マシンタイプ)であるM0は、1つのProjectで1つしか作れません。

Node.jsから接続準備

プロジェクトフォルダを作成→フォルダに移動→初期化
mongodb (MongoDBのドライバー)をインストール

mkdir monogodb-test
cd monogodb-test
npm init -y

#mongodbインストール
npm install mongodb --save

Atlasにログインします

  1. MongoDB Atlasの管理画面

    左サイド「Database」からクラスターの「Connect」ボタンをクリック

  2. MongoDB Atlasの管理画面

    クラスター(データベース)に接続する方法の選択肢です
    「Connect Your Application」を選択します

  3. MongoDB Atlasの管理画面

    「Node.js」を選択して、application codeをコピーします

mongodb.jsファイルを作成

//MongoClientクラスは、MongoDBへの接続を可能にするクラスです
const { MongoClient } = require("mongodb");

const uri ="コピーしたコードを貼り付けて、パスワードを編集します";
//MongoClientを使用して接続する
const client = new MongoClient(uri);

MonogoDBの構造と接続

MySQLとの比較
データベース:MySQLと同じ意味のデータベース
コレクション:MySQLのテーブルで、ドキュメントの集まりです
ドキュメント:MySQLのレコードで、JSONの形式で情報が入っています
_id:一意の値がが自動で付与されます

MonogoDBのデータベースイメージ

サーバー接続と終了

#サーバーに接続 戻り値Promise
client.connect(callback)

#接続先データベースを指定
client.db('データベース名')

#サーバー接続を終了
client.close(callback)

接続確認をします
monog.jsファイルに追記して、ターミナルでnode mongodb.js 実行
接続成功と表示されます

async function run() {
  try {
    // サーバーに接続
    await client.connect();
    // pingコマンドを実行して接続の確認する
    await client.db("admin").command({ ping: 1 });
    console.log("接続成功");
  } finally {
    // 接続を終了
    await client.close();
  }
}
run().catch(console.log);

コレクションの選択・作成 ・削除

#指定したデータペースに対して
const database = client.db('データベース名', callback)
#コレクションの選択、存在しない場合は、暗黙的に作成されます
database.collection('コレクション名', callback);
#コレクションの削除
database.dropCollection('コレクション名', callback);

*ほぼPromiseが返ります

ドキュメントのCRUD操作

ドキュメントの挿入
insertOne()単一のドキュメントを挿入します
*_idフィールドがない場合は、自動で追加されます

//データベース名
const databaseName = 'sample';
//コレクション名
const collectionName = 'foods';
const database = client.db(databaseName);
const collection = database.collection(collectionName)

async function run() {
  try {
    await client.connect();
      //ドキュメント
      const doc = { name: "Pudding", healthy: true };
      const result =  await collection.insertOne(doc);
      console.log(result);
  } finally {
    await client.close();
  }
}
run().catch(console.log);

出力結果

{
  acknowledged: true,
  insertedId: new ObjectId("62a7c57a6e286ddebca4d64d")
}

複数のドキュメントを挿入する
insertMany()
*オプションのordered: true は失敗した場合に、残りのドキュメントが挿入されない指定

async function run() {
  try {
      await client.connect();
      //ドキュメント
        const docs = [
        { name: "cake", healthy: false },
        { name: "lettuce", healthy: true },
        { name: "donut", healthy: false }
        ];
      const result =  await collection.insertMany(docs, {ordered: true });
      console.log(result);
  } finally {
    await client.close();
  }
}
run().catch(console.log);
{
  acknowledged: true,
  insertedCount: 3,
  insertedIds: {
    '0': new ObjectId("62a7f3b65a4254e9445ab152"),
    '1': new ObjectId("62a7f3b65a4254e9445ab153"),
    '2': new ObjectId("62a7f3b65a4254e9445ab154")
  }
}

ドキュメントを探す
findOne()の第一引数に検索クエリ、第二引数に、sort(並べ替え)やprojection(返却に含めるフィールド)を指定できます
*注意:最初に一致したドキュメントのみが返されます
クエリに一致するドキュメントがないときは空のカーソルを返し、クエリを指定しないときはコレクション内のすべてのドキュメントを返します

async function run() {
  try {
      await client.connect();
      const query = { healthy: false };
      const options = {
      projection: { _id: 0, name: 1 },
      };
    const result = await collection.findOne(query, options);
      console.log(result);
  } finally {
    await client.close();
  }
}
run().catch(console.log);
{ name: 'cake' }

複数のドキュメントを検索する場合は、find()メソッドを使用します
find()メソッドはCursorを返し、返却されたドキュメントを反復処理するには
next()toArray()forEach()を使用します

async function run() {
  try {
      await client.connect();
      const query = { healthy: false };
      const options = {
      projection: { _id: 0, name: 1 },
      };
   const result = await collection.find(query, options);
    if (( result.countDocuments) === 0) {
        console.log("検索結果なし");
        }
    await result.forEach(console.dir);
  } finally {
    await client.close();
  }
}
run().catch(console.log);
{ name: 'cake' }
{ name: 'donut' }

クエリの比較演算子・論理演算子・要素演算子・評価演算子について

$gt「より大きい」・$lt「より小さい」や $and 「かつ」・$or「または」を使用した例

// > 5
{rating: { $gt: 5 }}
//1< かつ >5
 $and: [ {{ rating: { $gt: 1 }, rating: { $lt: 5 }}, }]

単一のドキュメントを更新する
updateOne(filter, updateDoc, options)
第一引数はフィルタードキュメント(更新対象のドキュメント)
第二引数は更新ドキュメント
更新ドキュメントには更新演算子が1つ以上含まれます($set:フィールドの値を指定された値に置き換えます)
オプションにupsert: trueを設定すると一致するドキュメントがない場合はドキュメントを作成します

async function run() {
  try {
      await client.connect();
      const filter = { name: 'cake' };
      const updateDoc = { $set: {healthy: true}};
      const result = await collection.updateOne(filter, updateDoc,  { upsert: true });
      console.log(result);
  } finally {
    await client.close();
  }
}
run().catch(console.log);
{
  acknowledged: true,
  modifiedCount: 1,
  upsertedId: null,
  upsertedCount: 0,
  matchedCount: 1
}

複数のドキュメントを更新する場合は、updateMany()を使用します

//healthy:trueをすべてhealthy:falseに
async function run() {
  try {
      await client.connect();
      const filter = { healthy: true };
      const updateDoc = { $set: {healthy: false}};
      const result = await collection.updateMany(filter, updateDoc);
      console.log(result);
  } finally {
    await client.close();
  }
}
run().catch(console.log);

ドキュメントを削除する
deleteOne()は、最初の一致を削除します
deleteMany()は、複数のドキュメントを削除します

async function run() {
  try {
    await client.connect();
    const result = await collection.deleteMany({
        healthy: false
    })
    if (( result.countDocuments) === 0) {
        console.log("削除なし");
        }
    console.log(result);
  } finally {
    await client.close();
  }
}
run().catch(console.log);

Mongooseを試してみる

MongoDBをNode .jsで利用する方法

  • データベースのネイティブクエリを利用する方法(MongoDB Driverをインストール)
  • オブジェクトデータモデル (ODM)ツールを利用する方法(Mongooseをインストール)

Mongooseは、オブジェクトを「MongoDBのドキュメントデータベース」にマップして適切なクエリとスキーマに変換します(オブジェクトを直接実行できます)

スキーマをモデル(ドキュメントを作成するためのクラス)にコンパイル
ドキュメントは、 saveメソッドでデータベースに保存します
モデルを介してドキュメントにアクセスします
MongoDBのクエリ構文(検索で利用)がサポートされています

npm install mongoose --save

モデル用のmodels.jsを作成
モデルは、ドキュメントを作成するためのクラスです
Schemaにモデルのフィールドを定義します(文字列型:String・数値型:Number・真偽型:Booleanなど)

const mongoose = require('mongoose');
//スキーマの定義
const blogSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
  },
  body: {
    type: String,
    required: true
  },
}, { timestamps: true });

//スキーマをモデルにコンパイル
const Blog = mongoose.model('Blog', blogSchema);

module.exports = Blog;

モデルのフィールドにはvalidateやdefaultなどオプションが設定できます(validateの設定例)

validate(value) {
  if (value < 0) throw new Error("0以上");
},

新規作成
新しい記事を作成してデータベースに保存
Expressでデータベースに接続してルーティング(/blogでPOSTリクエスト)処理を書き
PostmanでリクエストBodyを作成してPostリクエストを送信します

postmanの画面
const express = require("express");
const app = express();
const mongoose = require("mongoose");
//モデルを読み込む
const Blog = require("./models.js")
const uri ="mongodb+srv:・・・・・";
//4.x系ではbody-parserのインストールは不要JSONをパースするミドルウェア
app.use(express.json());
//データベースサーバーに接続
mongoose.connect(uri)

app.post("/blog", async (request, response) => {
 //Blogモデルのインスタンスを作成
const blog = new Blog(request.body);
  try {
 //save()でデータベースに保存
    await blog.save();
    response.send(blog);
  } catch (error) {
    response.status(500).send(error);
  }
});

参考


ドキュメント全体の表示
localhost:3000/blogsにブラウザからアクセス(getリクエスト)すると保存したドキュメントがJSONで表示されます
find({})

app.get("/blogs", async (request, response) => {
    try {
    // find()は、パラメータが一致するすべてのオブジェクトを返します
    const blogs = await Blog.find({});
    response.send(blogs);
  } catch (error) {
    response.status(500).send(error);
  }
});

_idで1件だけ取得します
findById(id)
localhost:3000//blog/id(前に作成した記事の_id)でブラウザからアクセス(getリクエスト)します

app.get("/blog/:id", async (request, response) => {
  const id = request.params.id;
  try {
    const blog = await Blog.findById(id);
    response.send(blog);
  } catch (error) {
    response.status(500).send(error);
  }
});

_idを指定して、内容を更新します
findByIdAndUpdate()
PostmanでURLをlocalhost:3000//blog/id(前に作成した記事の_id)でpatchリクエストを送信します

app.patch("/blog/:id", async (request, response) => {
  const id = request.params.id;
  try {
//findByIdAndUpdate()de、_idで検索したターゲットとそれを置き換えるリクエストデータを取得します
    await Blog.findByIdAndUpdate(id, request.body);
 //save()で変更を保存します
    await Blog.save();
   response.status(200).send();
  } catch (error) {
    response.status(500).send(error);
  }
});

_idを指定して、削除します
findByIdAndDelete(id)
PostmanでURLをlocalhost:3000//blog/id(前に作成した記事の_id)でdeleteリクエストを送信します

app.delete("/blog/:id", async (request, response) => {
  const id = request.params.id;
    try {
    //_idで検索したターゲットを削除します
    await Blog.findByIdAndDelete(id);
    response.status(200).send();
  } catch (error) {
    response.status(500).send(error);
  }
});

mongooseは、コレクション名の末尾に「s」が付いていることを前提としていて、モデル名の小文字バージョンを検索するので、例えば、Blogモデルはデータベース内のblogsコレクションになります