Node.jsでMongooseからMongoDBを利用する
日付 タグ node.js mongodb カテゴリ node.js目次
Node.jsでMongooseからMongoDBを利用する
以前の記事でnode-mongodb-nativeを利用してNode.jsからMongoDBを扱う記事を書いた。
( ==> Node.jsからMongoDBを利用する )
MongoDBを扱うにはMongooseというORMも有名で良く利用されているので、今回の記事ではMongooseを利用してNode.jsからMongoDBのデータを扱うサンプルを作ってみる。
https://www.npmjs.com/package/mongoose - Mongoose
なお、今回作ったサンプルのソースコードは参照用としてGithubの以下に置いてある。 https://github.com/hugodeblog/node-mongoose
テストディレクトリ作成とnode-mongodb-nativeのインストール
今回もテストディレクトリの作成とモジュールのインストールから
$ mkdir node-mongoose
$ cd node-mongotest
$ npm init
$ npm install mongoose --save
本記事を書いている時点ではmongooseはバージョン5.11.12が入ったようだ。
Mongooseを利用するアプリ
今回は簡単にMongoDBのデータの作成、更新、削除のような基本操作ができるかをテストするアプリを作る。
データは朝食、昼食、夕食に何を食べたかメモ書きするような目的、データを想定した。
mongoose-test.mjs
import mongoose from 'mongoose';
import util from 'util';
const Schema = mongoose.Schema;
// 食事のタイプを定義
const MealTypeSchema = new Schema({
_id: Number,
type: String
});
const MealTypeModel = mongoose.model('MealType', MealTypeSchema);
// mealtypes.jsonにデータが入っているので、それを予めMongoDBに登録しておく
// mongoimport -h localhost --db meal --collection mealtypes --drop --jsonArray --file mealtypes.json
// 食事データを定義
const MealSchema = new Schema({
type: { type: Number, ref: 'MealType' },
foods: [{
menu: String
}]
});
const MealModel = mongoose.model('Meal', MealSchema);
var db;
const dbname = 'meal';
// MongoDBへの接続
const connectDB = async () => {
try {
await mongoose.connect(`mongodb://localhost/${dbname}`);
db = mongoose.connections;
console.log(util.inspect(db));
console.log(`Connected to database on Worker process: ${process.pid}`);
} catch (err) {
console.error(`Connection error: ${error.stack} on Worker process: ${process.pid}`);
process.exit(1)
}
}
// MongoDB接続切る
const closeDB = async () => {
console.log('closeDB called');
await mongoose.disconnect();
db = undefined;
}
// DB接続コールバック
mongoose.connection.on('connected', () => {
console.log('connected');
});
mongoose.connection.on('error', (err) => {
console.log('connection error: ' + err);
});
mongoose.connection.on('disconnected', () => {
console.log('disconnected');
});
mongoose.connection.on('close', () => {
console.log('connection closed');
});
(async () => {
try {
// DB接続
await connectDB();
var monday_morning = new MealModel({
type: 1,
foods: [
{ menu: '目玉焼き' },
{ menu: '牛乳' },
{ menu: 'トースト' }
]
});
await monday_morning.save();
var monday_lunch = new MealModel({
type: 2,
foods: [
{ menu: 'ナポリタン' },
{ menu: 'アイスコーヒー' }
]
});
await monday_lunch.save();
var monday_dinner = new MealModel({
type: 3,
foods: [
{ menu: '肉じゃが' },
{ menu: '納豆' },
{ menu: '味噌汁' }
]
});
await monday_dinner.save();
var thuesday_morning = new MealModel({
type: 1,
foods: [
{ menu: 'スクランブルエッグ' },
{ menu: '牛乳' },
{ menu: 'トースト' }
]
});
await thuesday_morning.save();
// 朝食に食べたのを検索
console.log('朝食に食べたのを検索します');
console.log('------------------');
{
const result = await MealModel.find({ type: 1 }).populate('type').exec();
console.log(JSON.stringify(result, null, 2));
}
// 肉じゃががメニューに入っていたのを検索
console.log('肉じゃががメニューに入っていたのを検索します');
console.log('------------------');
{
const result = await MealModel.find({ 'foods.menu': '肉じゃが' }).populate('type').exec();
console.log(JSON.stringify(result, null, 2));
}
// トーストがメニューに入っていたのを検索
console.log('トーストがメニューに入っていたのを検索します');
console.log('------------------');
{
const result = await MealModel.find({ 'foods.menu': 'トースト' }).populate('type').exec();
console.log(JSON.stringify(result, null, 2));
}
// 牛乳 => オレンジジュースを入れ替え
console.log('牛乳 => オレンジジュースを入れ替えします');
console.log('------------------');
{
const result = await MealModel.updateMany(
{ 'foods.menu': '牛乳' },
{ '$set': { 'foods.$.menu': 'オレンジジュース' } }
);
console.log(JSON.stringify(result, null, 2));
}
// 全コレクションをチェック
console.log('全コレクションをチェックします');
console.log('------------------');
{
const result = await MealModel.find().populate('type').exec();;
console.log(JSON.stringify(result, null, 2));
}
// 夕食の全データを削除
console.log('夕食の全データを削除します');
console.log('------------------');
{
const result = await MealModel.deleteMany({ type: 3 });
console.log(JSON.stringify(result, null, 2));
}
// 全コレクションをチェック
console.log('全コレクションをチェックします');
console.log('------------------');
{
const result = await MealModel.find().populate('type').exec();;
console.log(JSON.stringify(result, null, 2));
}
} catch (err) {
console.error(err);
} finally {
// DBクローズ
await closeDB();
}
})();
スキーマとデータモデル
Mongooseでデータを扱うには、どのようなデータ型を持つデータがコレクションに入るかというスキーマ定義が必要になる。
// 食事のタイプを定義 const MealTypeSchema = new Schema({ _id: Number, type: String }); // 食事データを定義 const MealSchema = new Schema({ type: { type: Number, ref: 'MealType' }, foods: [{ menu: String }] });
そこで朝食、昼食、夕食というカテゴリー分けのためにMealTypeSchema というスキーマ定義を作り、
mealtypes.json
[
{
"_id": 1,
"type": "朝食"
},
{
"_id": 2,
"type": "昼食"
},
{
"_id": 3,
"type": "夕食"
}
]
上記のデータを持つようにする。その上でMealSchema スキーマを定義している。
これらの定義したスキーマからデータモデルを作成しているのは以下。
const MealTypeModel = mongoose.model('MealType', MealTypeSchema); const MealModel = mongoose.model('Meal', MealSchema);
これによりMongoDB上のcollection名としては、それぞれmealtypes (複数形)、meals (複数形)として保存されることになる。
例えば、月曜日の朝食に食べたメニューデータを追加したい場合は、
var monday_morning = new MealModel({ type: 1, foods: [ { menu: '目玉焼き' }, { menu: '牛乳' }, { menu: 'トースト' } ] });
のようなデータを用意すればいいことになる。
データ定義が準備できたところでMongoDBに接続してデータを保存してみたりしよう。
MongoDBへの接続と解除
MongoDBヘの接続としては、ローカルPC環境上で動作しているMongoDBに接続することを前提として、データベース名をmeal として
const dbname = 'meal'; await mongoose.connect(`mongodb://localhost/${dbname}`);
アプリ終了時にはMongoDBヘの接続を解除するため、
await mongoose.disconnect();
上記を実行している。
MongoDBヘ接続されたタイミングなどはコールバックを利用すれば把握できる。
// DB接続コールバック mongoose.connection.on('connected', () => { console.log('connected'); }); mongoose.connection.on('error', (err) => { console.log('connection error: ' + err); }); mongoose.connection.on('disconnected', () => { console.log('disconnected'); }); mongoose.connection.on('close', () => { console.log('connection closed'); });
MongoDBのデータを生成する
var monday_morning = new MealModel({ type: 1, foods: [ { menu: '目玉焼き' }, { menu: '牛乳' }, { menu: 'トースト' } ] }); await monday_morning.save();
月曜の朝食メニューの登録だが、上記のようにモデルのインスタンスを作った上でsave() メソッドを呼び出せば良い。
MongoDBのコレクションからデータ読み込み
登録されたデータから朝食データを抜き出したい場合、type: 1 のデータを検索すれば良いので、
await MealModel.find({ type: 1 }).populate('type').exec();
として検索している。
populate はtype の参照情報を取ってきて、結果表示するかどうか。
populate した結果
朝食に食べたのを検索します
------------------
[
{
"_id": "60069901ed2e6dfc6995efbf",
"type": {
"_id": 1,
"type": "朝食"
},
"foods": [
{
"_id": "60069901ed2e6dfc6995efc0",
"menu": "目玉焼き"
},
{
"_id": "60069901ed2e6dfc6995efc1",
"menu": "牛乳"
},
{
"_id": "60069901ed2e6dfc6995efc2",
"menu": "トースト"
}
],
"__v": 0
},
{
"_id": "60069901ed2e6dfc6995efca",
"type": {
"_id": 1,
"type": "朝食"
},
"foods": [
{
"_id": "60069901ed2e6dfc6995efcb",
"menu": "スクランブルエッグ"
},
{
"_id": "60069901ed2e6dfc6995efcc",
"menu": "牛乳"
},
{
"_id": "60069901ed2e6dfc6995efcd",
"menu": "トースト"
}
],
"__v": 0
}
]
populate を外した場合
朝食に食べたのを検索します
------------------
[
{
"_id": "600699f84cf54bfe24a9bf6d",
"type": 1,
"foods": [
{
"_id": "600699f84cf54bfe24a9bf6e",
"menu": "目玉焼き"
},
{
"_id": "600699f84cf54bfe24a9bf6f",
"menu": "牛乳"
},
{
"_id": "600699f84cf54bfe24a9bf70",
"menu": "トースト"
}
],
"__v": 0
},
{
"_id": "600699f84cf54bfe24a9bf78",
"type": 1,
"foods": [
{
"_id": "600699f84cf54bfe24a9bf79",
"menu": "スクランブルエッグ"
},
{
"_id": "600699f84cf54bfe24a9bf7a",
"menu": "牛乳"
},
{
"_id": "600699f84cf54bfe24a9bf7b",
"menu": "トースト"
}
],
"__v": 0
}
]
MongoDBのデータの更新
月曜の朝食と火曜日の朝食のメニューに牛乳が入っていたが、データを誤って登録していたので、これをオレンジジュースに書き換えたいという場合
await MealModel.updateMany( { 'foods.menu': '牛乳' }, { '$set': { 'foods.$.menu': 'オレンジジュース' } } );
updateMany は該当するデータが複数あった場合、それらを全部更新するということである。
最初にマッチするのを1つのみ更新したい場合はupdateOne を使う。
MongoDBのデータの削除
夕食のデータを全部削除したいのであれば、
await MealModel.deleteMany({ type: 3 });
上記のようにdeleteMany を使う。
最初にマッチする1つのみ削除したい場合であれば、deleteOne を使う。
サンプルアプリの実行
最初にMongoDBのサーバーをローカルで立ち上げておく。
その上でMealTypeSchema として、mealtypes.json を以下のコマンドで読み込ませておく。
$ mongoimport -h localhost --db meal --collection mealtypes --drop --jsonArray --file mealtypes.json
ここで一旦MongoDBにcliで接続してデータを見てみよう。
$ mongo
> use meal
switched to db meal
> db.mealtypes.find()
{ "_id" : 3, "type" : "夕食" }
{ "_id" : 2, "type" : "昼食" }
{ "_id" : 1, "type" : "朝食" }
>
参照されるデータは正しく登録されているようだ。
その上で、アプリを実行してみよう。
$ node mongoose-test.mjs
Connected to database on Worker process: 67330
朝食に食べたのを検索します
------------------
[
{
"_id": "60069f5d7e898d0703f3a463",
"type": {
"_id": 1,
"type": "朝食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a464",
"menu": "目玉焼き"
},
{
"_id": "60069f5d7e898d0703f3a465",
"menu": "牛乳"
},
{
"_id": "60069f5d7e898d0703f3a466",
"menu": "トースト"
}
],
"__v": 0
},
{
"_id": "60069f5d7e898d0703f3a46e",
"type": {
"_id": 1,
"type": "朝食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a46f",
"menu": "スクランブルエッグ"
},
{
"_id": "60069f5d7e898d0703f3a470",
"menu": "牛乳"
},
{
"_id": "60069f5d7e898d0703f3a471",
"menu": "トースト"
}
],
"__v": 0
}
]
肉じゃががメニューに入っていたのを検索します
------------------
[
{
"_id": "60069f5d7e898d0703f3a46a",
"type": {
"_id": 3,
"type": "夕食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a46b",
"menu": "肉じゃが"
},
{
"_id": "60069f5d7e898d0703f3a46c",
"menu": "納豆"
},
{
"_id": "60069f5d7e898d0703f3a46d",
"menu": "味噌汁"
}
],
"__v": 0
}
]
トーストがメニューに入っていたのを検索します
------------------
[
{
"_id": "60069f5d7e898d0703f3a463",
"type": {
"_id": 1,
"type": "朝食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a464",
"menu": "目玉焼き"
},
{
"_id": "60069f5d7e898d0703f3a465",
"menu": "牛乳"
},
{
"_id": "60069f5d7e898d0703f3a466",
"menu": "トースト"
}
],
"__v": 0
},
{
"_id": "60069f5d7e898d0703f3a46e",
"type": {
"_id": 1,
"type": "朝食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a46f",
"menu": "スクランブルエッグ"
},
{
"_id": "60069f5d7e898d0703f3a470",
"menu": "牛乳"
},
{
"_id": "60069f5d7e898d0703f3a471",
"menu": "トースト"
}
],
"__v": 0
}
]
牛乳 => オレンジジュースを入れ替えします
------------------
{
"n": 2,
"nModified": 2,
"ok": 1
}
全コレクションをチェックします
------------------
[
{
"_id": "60069f5d7e898d0703f3a463",
"type": {
"_id": 1,
"type": "朝食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a464",
"menu": "目玉焼き"
},
{
"_id": "60069f5d7e898d0703f3a465",
"menu": "オレンジジュース"
},
{
"_id": "60069f5d7e898d0703f3a466",
"menu": "トースト"
}
],
"__v": 0
},
{
"_id": "60069f5d7e898d0703f3a467",
"type": {
"_id": 2,
"type": "昼食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a468",
"menu": "ナポリタン"
},
{
"_id": "60069f5d7e898d0703f3a469",
"menu": "アイスコーヒー"
}
],
"__v": 0
},
{
"_id": "60069f5d7e898d0703f3a46a",
"type": {
"_id": 3,
"type": "夕食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a46b",
"menu": "肉じゃが"
},
{
"_id": "60069f5d7e898d0703f3a46c",
"menu": "納豆"
},
{
"_id": "60069f5d7e898d0703f3a46d",
"menu": "味噌汁"
}
],
"__v": 0
},
{
"_id": "60069f5d7e898d0703f3a46e",
"type": {
"_id": 1,
"type": "朝食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a46f",
"menu": "スクランブルエッグ"
},
{
"_id": "60069f5d7e898d0703f3a470",
"menu": "オレンジジュース"
},
{
"_id": "60069f5d7e898d0703f3a471",
"menu": "トースト"
}
],
"__v": 0
}
]
夕食の全データを削除します
------------------
{
"n": 1,
"ok": 1,
"deletedCount": 1
}
全コレクションをチェックします
------------------
[
{
"_id": "60069f5d7e898d0703f3a463",
"type": {
"_id": 1,
"type": "朝食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a464",
"menu": "目玉焼き"
},
{
"_id": "60069f5d7e898d0703f3a465",
"menu": "オレンジジュース"
},
{
"_id": "60069f5d7e898d0703f3a466",
"menu": "トースト"
}
],
"__v": 0
},
{
"_id": "60069f5d7e898d0703f3a467",
"type": {
"_id": 2,
"type": "昼食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a468",
"menu": "ナポリタン"
},
{
"_id": "60069f5d7e898d0703f3a469",
"menu": "アイスコーヒー"
}
],
"__v": 0
},
{
"_id": "60069f5d7e898d0703f3a46e",
"type": {
"_id": 1,
"type": "朝食"
},
"foods": [
{
"_id": "60069f5d7e898d0703f3a46f",
"menu": "スクランブルエッグ"
},
{
"_id": "60069f5d7e898d0703f3a470",
"menu": "オレンジジュース"
},
{
"_id": "60069f5d7e898d0703f3a471",
"menu": "トースト"
}
],
"__v": 0
}
]
closeDB called
disconnected
connection closed
以上、Mongooseを利用してデータ作成、更新、削除、閲覧ができることが確認できた。