mongodb-mongoose について
mongodb-mongooseは、Node.jsやNext.jsでMongoDBを扱う開発者向けのデータモデリングとMongooseのガイドです。スキーマ設計、クエリ最適化、Atlas接続、移行を扱い、変更の検証手順も示します。
主な機能
- 01クエリパターンに沿ったMongoDBドキュメント設計
- 02Mongooseモデル、検証、ミドルウェアのガイド
- 03リポジトリパターンと集約パイプラインの例
- 04Atlas接続と移行戦略
- 05クエリ性能分析と検証ワークフロー
活用シーン
- 01アプリのコレクションとドキュメント構造の設計
- 02検証付きMongooseモデルの実装
- 03集約クエリの作成と性能確認
- 04データベース移行の計画と変更の検証
MCP 対応
MongoDB MCPの利用を推奨しています。利用できない場合は、mongosh、Atlas UI、ローカルのスキーマファイル、モデル検査を使う代替手順が示されています。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ practicalswan/ agent-skills --skill mongodb-mongooseインストール後、Claude Code で/mongodb-mongooseを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
CHANGELOG.mdLICENSE.txtexamples/recipe-api-example.mdreferences/aggregation-reference.mdindexing-strategies.mdscripts/seed-database.js詳細
Mongodb Mongoose
現行の MongoDB サーバーリリース、Mongoose 8.x 以降、Node.js 22 以降、TypeScript 5.5 以降に最適化されています。
Node.js/Next.js アプリケーション向けに、MongoDB データベース設計、Mongoose ODM のパターン、Atlas 統合に関する包括的なガイダンスを提供します。
- 利用可能な場合は、ネイティブの並列サブエージェントのディスパッチと 200k 以上のコンテキストウィンドウを活用してください。
このスキルを使用するタイミング
症状 → アクションのトリガーを使用します。該当するものがあれば、このスキルを適用し、以下のプロトコルで検証してください。
- MongoDB のスキーマとデータモデルを設計する
- バリデーションとミドルウェアを備えた Mongoose モデルを構築する
- データアクセスにリポジトリパターンを実装する
- 複雑なクエリのために集約パイプラインを作成する
- MongoDB Atlas の接続と構成を管理する
- MongoDB を Next.js の API ルートと統合する
- データベースの移行戦略
クライアント間の移植性
このスキルは、GitHub Copilot、Claude Code、Codex で利用できるように作成されています。
- GitHub Copilot:フォルダーを Copilot から参照可能なスキルパスに置くか、フォルダーを検出できない場合はワークフローをプロジェクトの指示でラップします。
- Claude Code:フォルダーをローカルのスキルディレクトリまたは互換性のあるプラグインソースに置きます。
- Codex:フォルダーを
$CODEX_HOME/skills/mongodb-mongooseにインストールまたは同期し、大きな変更を加えた後は Codex を再起動します。
MCP の利用可否とフォールバック
優先する MCP サーバー:MongoDB MCP
- フォールバックプロンプト:「MCP を使用せずに Mongodb Mongoose スキルを使ってください。文書化されたローカルまたは手動のフォールバックに従い、選択したツールの範囲を示して、検証の根拠を報告してください。」
- MCP サーバーが利用できない場合は、
mongosh、MongoDB Atlas UI、ローカルのスキーマファイル、Mongoose モデルの検査を使用します。 - 変更を確定する前に、ローカルまたはステージングのデータベースに対してインデックス、クエリ、集約パイプラインを検証します。
- アクティブなホストで MCP が公開されていない場合は、MCP 操作を使用したと主張しないでください。
アンチパターン
- 既定でドキュメントを正規化されたテーブルのようにモデル化すること:MongoDB のパフォーマンスは、リレーショナルデータとしての純粋性ではなく、クエリに応じたデータ形状に左右されます。
- すべてのリクエストで完全にハイドレートされたドキュメントを返すこと:API パスでは、過剰なデータ取得とハイドレーションのオーバーヘッドが急速に蓄積します。
- 書き込みパスのテストなしにミドルウェアを追加すること:フックによって、作成、更新、移行の動作が気づかないうちに変化することがあります。
検証プロトコル
「スキルの適用に成功しました」と主張する前に:
- 合否:Mongodb Mongoose の実装で、対象ランタイム、フレームワークのバージョン、影響を受けるファイルが明示されています。
- 合否:変更対象について、ビルド、リント、テスト、または同等のローカル検証が実行されています。
- 合否:エラー、依存関係のドリフト、環境差異に関するエッジケースに対処するか、明示的に対象外としています。
- 負荷テストシナリオ:ハッピーパスのテストには通るものの、境界条件で失敗する変更にワークフローを適用します。
- 成功指標:未検証の成功主張がゼロであること。すべての実装に関する主張が、コマンドまたは成果物に対応しています。
変更前後の例
// Before
const recipes = await Recipe.find({ author: userId }).populate('author');
// After
const recipes = await Recipe.find({ author: userId, isPublished: true })
.select({ title: 1, slug: 1, createdAt: 1 })
.sort({ createdAt: -1 })
.lean();
クエリの形状を絞り込み、不要なハイドレーションを避け、実際に必要なビュー・モデルに結果を合わせます。
スキーマ設計
データモデリングの原則
- データにまとめてアクセスし、1 対少数の関係がある場合は埋め込みます
- データに個別にアクセスする場合、または 1 対多/多対多の関係がある場合は参照します
- 正規化されたリレーショナルモデルではなく、クエリパターンを中心にスキーマを設計します
- 読み取りパフォーマンスのために、戦略的に非正規化を使用します
Mongoose モデルのパターン
import mongoose from 'mongoose';
const recipeSchema = new mongoose.Schema({
title: {
type: String,
required: [true, 'Title is required'],
trim: true,
maxlength: [200, 'Title cannot exceed 200 characters'],
index: true,
},
slug: {
type: String,
unique: true,
lowercase: true,
},
ingredients: [{
name: { type: String, required: true },
amount: { type: Number, required: true },
unit: { type: String, enum: ['g', 'kg', 'ml', 'l', 'cup', 'tbsp', 'tsp', 'piece'] },
}],
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true,
},
tags: [{ type: String, lowercase: true, trim: true }],
isPublished: { type: Boolean, default: false },
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true },
});
// Indexes for common queries
recipeSchema.index({ title: 'text', tags: 'text' });
recipeSchema.index({ author: 1, createdAt: -1 });
// Virtual fields
recipeSchema.virtual('ingredientCount').get(function() {
return this.ingredients.length;
});
// Pre-save middleware
recipeSchema.pre('save', function(next) {
if (this.isModified('title')) {
this.slug = this.title.toLowerCase().replace(/[^a-z0-9]+/g, '-');
}
next();
});
export const Recipe = mongoose.models.Recipe || mongoose.model('Recipe', recipeSchema);
スキーマのベストプラクティス
required、type、バリデーションルールを必ず定義しますtimestamps: trueを使用してcreatedAt/updatedAtを自動設定します- 頻繁にクエリされるフィールドにインデックスを追加します
- 固定値を持つフィールドには
enumを使用します - 計算プロパティには仮想プロパティを定義します
- 副作用にはミドルウェア(pre/post フック)を使用します
リポジトリパターン
class RecipeRepository {
async findAll(filter = {}, options = {}) {
const { page = 1, limit = 20, sort = '-createdAt', populate = '' } = options;
const skip = (page - 1) * limit;
const [recipes, total] = await Promise.all([
Recipe.find(filter)
.sort(sort)
.skip(skip)
.limit(limit)
.populate(populate)
.lean(),
Recipe.countDocuments(filter),
]);
return {
data: recipes,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit),
},
};
}
async findById(id) {
return Recipe.findById(id).populate('author', 'name avatar').lean();
}
async create(data) {
const recipe = new Recipe(data);
return recipe.save();
}
async update(id, data) {
return Recipe.findByIdAndUpdate(id, data, {
new: true,
runValidators: true,
});
}
async delete(id) {
return Recipe.findByIdAndDelete(id);
}
async search(query, options = {}) {
return this.findAll(
{ $text: { $search: query } },
{ ...options, sort: { score: { $meta: 'textScore' } } }
);
}
}
export const recipeRepository = new RecipeRepository();
集約パイプライン
よく使用されるパターン
// Group recipes by tag with counts
const tagStats = await Recipe.aggregate([
{ $match: { isPublished: true } },
{ $unwind: '$tags' },
{ $group: { _id: '$tags', count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: 20 },
]);
// Author statistics with lookup
const authorStats = await Recipe.aggregate([
{ $group: {
_id: '$author',
recipeCount: { $sum: 1 },
avgRating: { $avg: '$rating' },
}},
{ $lookup: {
from: 'users',
localField: '_id',
foreignField: '_id',
as: 'authorInfo',
}},
{ $unwind: '$authorInfo' },
{ $project: {
name: '$authorInfo.name',
recipeCount: 1,
avgRating: { $round: ['$avgRating', 1] },
}},
{ $sort: { recipeCount: -1 } },
]);
// Date-based analytics
const monthlyRecipes = await Recipe.aggregate([
{ $match: { createdAt: { $gte: new Date('2024-01-01') } } },
{ $group: {
_id: { $dateToString: { format: '%Y-%m', date: '$createdAt' } },
count: { $sum: 1 },
}},
{ $sort: { _id: 1 } },
]);
Atlas 接続
接続の設定(Next.js)
import mongoose from 'mongoose';
const MONGODB_URI = process.env.MONGODB_URI;
if (!MONGODB_URI) {
throw new Error('MONGODB_URI environment variable is not defined');
}
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
export async function connectDB() {
if (cached.conn) return cached.conn;
if (!cached.promise) {
cached.promise = mongoose.connect(MONGODB_URI, {
bufferCommands: false,
});
}
cached.conn = await cached.promise;
return cached.conn;
}
接続のベストプラクティス
- 開発環境では接続をキャッシュし、複数の接続が作成されないようにします
- 明示的なエラー処理には
bufferCommands: falseを使用します - 本番環境では
maxPoolSizeで接続プールのサイズを設定します retryWrites=true&w=majorityを指定した Atlas 接続文字列を使用します
移行戦略
ドキュメントのバージョン管理
const userSchema = new mongoose.Schema({
schemaVersion: { type: Number, default: 2 },
// ... fields
});
userSchema.pre('save', function(next) {
if (this.schemaVersion < 2) {
// Migrate old fields to new format
this.schemaVersion = 2;
}
next();
});
バッチ移行スクリプト
async function migrateUsers() {
const batchSize = 100;
let processed = 0;
let batch;
do {
batch = await User.find({ schemaVersion: { $lt: 2 } }).limit(batchSize);
for (const user of batch) {
user.schemaVersion = 2;
await user.save();
processed++;
}
console.log(`Migrated ${processed} users`);
} while (batch.length === batchSize);
}
パフォーマンスのヒント
- 読み取り専用クエリには
.lean()を使用します(プレーンオブジェクトを返し、5~10 倍高速です) - 必要なフィールドのみを返すには
.select()を使用します - クエリのパターンに合った複合インデックスを作成します
- 作業セットを減らすため、集計では早い段階で
$projectを使用します - 高頻度のクエリでは
$lookupを避け、代わりに非正規化します explain()を使用してクエリのパフォーマンスを分析します
トラブルシューティング
| 問題 | 解決策 |
|---|---|
| クエリが遅い | インデックスを追加し、.lean() を使用して、explain() で確認します |
| 接続がタイムアウトする | Atlas のネットワークアクセスを確認し、接続プールのサイズを増やします |
| 検証エラー | スキーマの制約を見直し、ミドルウェアの順序を確認します |
| 重複キーエラー | 一意なインデックスを確保し、try/catch で処理します |
| メモリの問題 | 大規模なデータセットにはカーソルを使用し、バッチサイズを制限します |
よくある落とし穴
- デフォルトで正規化されたリレーショナルスキーマのようにデータをモデル化すること:MongoDB のパフォーマンスは、テーブルを先に設計する方法ではなく、クエリに応じたドキュメントの形状に左右されます。
- あらゆる場面で完全にハイドレートされたドキュメントを返すこと:lean なプロジェクションで十分な場合、ハイドレーションや過剰なデータ取得はコストを増やします。
- 明示的な書き込みパスのテストなしにミドルウェアを追加すること:フックは、作成、更新、移行の処理における動作をひそかに変えることがあります。
参考資料とリソース
ドキュメント
スクリプト
- データベースのシード — サンプルのレシピデータを使った、依存関係ゼロの MongoDB シードスクリプト
例
- レシピ API の例 — モデル、ルート、バリデーションを備えた、Mongoose + Next.js による完全なレシピ CRUD API
関連スキル
- javascript-development:ワークフローで最新の JavaScript と TypeScript によるアプリケーションコードも必要な場合に使用します。
- nextjs-development:ワークフローで Next.js App Router とサーバー優先の React パターンも必要な場合に使用します。
- sql-development:ワークフローで SQL クエリ、スキーマ、パフォーマンスのチューニング作業も必要な場合に使用します。
- code-quality:ワークフローで、2 段階のレビュー(まず仕様への準拠、次にコード品質)、保守性、リファクタリングに関する指針も必要な場合に使用します。
コメント
Markdown 対応