はじめに
本記事を投稿しようとおもったきっかけとして、
Gemini-CLIを使ってTrelloクローンアプリを作成しようと思い、
今までほぼRDBMSでDB管理するアプリを作っていた(一部Firebase経験あり)ので、
Firebase以外のNoSQLデータベースを使って作ろうと思い、
知名度のあるMongoDBを使ってCRUD処理を行う方針で実装しました。
※実装はGemini-CLIですが。
実装したのは良いものの、
内容が全然わからない状態なのと、
今ある実装をベースに学習をしたいと思い、本記事を作成しながら
学習しようと思った次第です。
※下記公式サイトは日本語表記サイトです。
本記事ではDBサーバーについて、Dockerで構築しますが、
Dockerについての詳しい内容は割愛します。
※Dockerの記事を別で投稿したいため。
また、実装内容はNext.jsベースでの記載となっております。
1.MongoDBとは
テーブルの代わりに柔軟なJSONドキュメントをデータとして格納する、
オープンソースのNoSQLドキュメントデータベース
主な特徴
-
NoSQLデータベース
リレーショナルデータベース(RDB)のように
テーブルと行でデータを管理するのではなく、
JSONライクなドキュメントを「コレクション」と呼ばれる単位でまとめて保存する。
JOIN不要で高速なデータ取得が可能。 -
スキーマレス
厳密なスキーマを必要としないため、
アプリケーションの機能変更に合わせてデータの構造を柔軟に変更可能。
※データの構造を事前に厳密に定義する必要がなく、
ドキュメントごとに異なるデータ構造が可能。 -
スケーラビリティ
シャーディング(データ分割)などの機能により、
データ量の増加に合わせてシステムを簡単に拡張が可能。
※シャーディングとは
大量のデータを複数のサーバー(データベース)に分割して保存する技術のこと。
-
高速な読み書き
データをメモリ上で処理することが多く、膨大な量のデータの高速な読み書きに対応
2.MongoDBの環境構築
本記事でのMongoDB構造
MongoDB Server
└── データベース: kanban_board
└── コレクション: boards
2.1 必要なライブラリのインストール
# MongoDB公式ドライバー
npm install mongodb
# 型定義(TypeScript使用時・必須)
npm install -D @types/node
2.2 Docker Composeでのサーバー構築
services:
mongodb:
image: mongo
container_name: mongodb
# 27017はMongoDBのデフォルトポート
ports:
- '27017:27017'
# データ永続化
volumes:
- mongo-data:/data/db
# データ永続化(コンテナ削除後もデータが保持される)
volumes:
mongo-data:
2.3 .env設定
# === MongoDB接続設定 ===
# Docker使用時(ローカル開発)
MONGODB_URI=mongodb://localhost:27017/kanban_board
MONGODB_DB_NAME=kanban_board
# MongoDB Atlas使用時(本番)
# MONGODB_URI=mongodb+srv://username:password@cluster0.xxxxx.mongodb.net/kanban_board?retryWrites=true&w=majority
3.MongoDBのCRUD操作
// === CREATE(作成)===
// 1件のドキュメント挿入
db.board.insertOne({
userId: "user123",
title: "My Board",
lists: [],
createdAt: new Date(),
updatedAt: new Date()
})
// 複数ドキュメント挿入
db.boards.insertMany([
{ userId: "user1", title: "Board 1" },
{ userId: "user2", title: "Board 2" }
])
// === READ(読み取り)===
// 全ドキュメント取得
db.boards.find()
// 条件指定で取得
db.boards.find({ userId: "user123" })
// 1件のみ取得
db.boards.findOne({ userId: "user123" })
// 配列内の要素で検索
db.boards.find({ "lists.id": "list1" })
// フィールド指定(プロジェクション)
// プロジェクション:MongoDBのクエリでドキュメントの取得フィールドを指定する機能
db.boards.find({}, { title: 1, userId: 1, _id: 0 })
// === UPDATE(更新)===
// 1件更新
db.boards.updateOne(
{ userId: "user123" },
{ $set: { title: "updated Board", updatedAt: new Date() } }
)
// 配列に要素追加
db.boards.updateOne(
{ userId: "user123" },
{ $push: { lists: { id: "list1", title: "To Do", tasks: [] }}}
)
// ネストした要素の更新
db.boards.updateOne(
{ userId: "user123", "lists.id": "list1" },
{ $set: { "lists.$.title": "New List Title" } }
)
// === DELETE(削除)===
// 1件削除
db.boards.deleteOne({ userId: "user123" })
// 配列から要素削除
db.boards.updateOne(
{ userId: "user123" },
{ $pull: { lists: { id: "list1" } } }
)
4.MongoDB関連の実装
4.1 MongoDB接続管理
import { MongoClient } from 'mongodb';
// 環境に応じた接続URI設定
const MONGODB_URI = process.env.MONGODB_URI;
// 開発環境でのHMR対応(ホットリロード時の接続維持)
if (process.env.NODE_ENV === 'development') {
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options);
global._mongoClientPromise = client.connect();
}
clientPromise = global._mongoClientPromise;
} else {
// 本番環境では毎回新しい接続
client = new MongoClient(uri, options);
clientPromise = client.connect();
}
4.2 CREATEサンプル
import { NextResponse } from 'next/server';
import clientPromise from '@/lib/mongodb';
import { type UpdateFilter, type Document } from 'mongodb';
import { taskSchema as baseTaskSchema } from '@/validation/boardValidation';
import { getUserIdFromSession } from '@/lib/auth-utils';
export async function POST(request: Request, context: unknown) {
// === パラメータ取得 ===
const { listId } = await (context as { params: { listId: string } }).params;
const { userId, title, content } = await request.json();
const newTask = { userId, title, content };
// === MongoDB接続取得 ===
const client = await clientPromise;
const db = client.db(process.env.MONGODB_DB_NAME || 'test');
const boardsCollection = db.collection('boards');
// === CRUD処理(CREATE)===
const filter = { userId, 'lists.id': listId } as unknown as Document;
const update = ({
$push: { 'lists.$.tasks': newTask as unknown as Document },
} as unknown) as UpdateFilter<Document>;
const result = await boardsCollection.updateOne(filter, update);
// === 結果確認 ===
if (result.matchedCount === 0 || result.modifiedCount === 0) {
return NextResponse.json({ message: 'リストが見つかりませんでした。' }, { status: 404 });
}
return NextResponse.json({ message: 'タスクが作成されました。' }, { status: 201 });
}
4.3 READサンプル
export async function GET() {
try {
// === 1. 認証処理(必須)===
const { userId } = await getUserIdFromSession();
// === 2. MongoDB接続取得 ===
const client = await clientPromise;
const db = client.db(process.env.MONGODB_DB_NAME || 'test');
const boardsCollection = db.collection('boards');
// === 3. CRUD処理(READ)===
// 既存ボードを取得
const existing = await boardsCollection.findOne({ userId });
if (existing) {
return NextResponse.json(existing);
}
return NextResponse.json({ message: 'ボードが見つかりませんでした。' }, { status: 404 });
}
4.4 UPDATEサンプル
import { NextResponse } from 'next/server';
import clientPromise from '@/lib/mongodb';
import { type UpdateFilter, type Document } from 'mongodb';
export async function PUT(request: Request, context: unknown) {
// === パラメータ取得 ===
const { listId } = (context as { params: { listId: string } }).params;
const { userId, title } = await request.json();
const updatedList = { userId, title };
// === MongoDB接続取得 ===
const client = await clientPromise;
const db = client.db(process.env.MONGODB_DB_NAME || 'test');
const boardsCollection = db.collection('boards');
// === CRUD処理(UPDATE)===
const result = await boardsCollection.updateOne(
// フィルター:ユーザーのボード内で該当するリストを特定
{ userId, 'lists.id': listId },
// 更新操作:配列内の該当リストのタイトルを更新
{ $set: { 'lists.$.title': updatedList.title } }
);
// === 結果確認 ===
if (result.matchedCount === 0) {
return NextResponse.json(
{ message: 'リストが見つかりませんでした。' },
{ status: 404 }
);
}
return NextResponse.json({ message: 'リストが更新されました。' });
}
4.5 DELETEサンプル
export async function DELETE(request: Request, context: unknown) {
// === パラメータ取得 ===
const { cardId } = (context as { params: { cardId: string } }).params;
const { listId, userId } = await request.json();
// === MongoDB接続取得 ===
const client = await clientPromise;
const db = client.db(process.env.MONGODB_DB_NAME || 'test');
const boardsCollection = db.collection('boards');
// === CRUD処理(DELETE)- 配列から要素削除 ===
const filter = { userId, 'lists.id': listId } as unknown as Document;
const updatePull: UpdateFilter<Document> = {
$pull: { 'lists.$[listElem].tasks': { id: cardId } },
// $pull:配列から要素削除
} as unknown as UpdateFilter<Document>;
const result = await boardsCollection.updateOne(
filter,
updatePull,
{ arrayFilters: [{ 'listElem.id': listId }] } // 特定リスト内のタスクを削除
);
// === 5. 結果確認 ===
if (result.matchedCount === 0 || result.modifiedCount === 0) {
return NextResponse.json({ message: 'タスクが見つかりませんでした。' }, { status: 404 });
}
return NextResponse.json({ message: 'タスクが削除されました。' });
}
5.MongoDBの注意点
updateOneについて
- updateOneは条件に合致した最初の1件のみ更新するため、
複数件更新の場合はupdateManyを利用すること。
ドキュメントのサイズ制限
- ドキュメントが16MBのサイズ制限があるので、
大きな配列や大量データがあると制限超過のリスクがある。
参照整合性の管理
- MongoDBには、外部キー制約がないので、手動で整合性を担保する必要がある
SQLインジェクションなどのセキュリティ対策
- MongoDBの場合、MongooseというORMライブラリがあるようです
(自分は使ってないです)
また、Prismaが使えるようです。(TypeScript対応)
6.Compass(GUIツール)、Atlas()
Compassとは
MongoDBのデータをGUI(グラフィカルユーザーインターフェース)で
操作できる公式ツールです。
これにより、データのクエリ、集計、インデックス管理、スキーマ調査、
インポート・エクスポートといった操作を直感的に行うことができ、
GUI環境がないOS(macOS, Windows, Linux)で
MongoDBを効率的に管理・分析が可能。
Atlasとは
Atlasは、MongoDBが提供するクラウドベースのデータベースサービス(DBaaS)
フルマネージドサービスとして、インフラ管理を自動化し、
開発者がアプリケーション開発に集中できるよう設計されています。
- マルチクラウド対応(AWS、Azure、GCP)
- 自動スケーリング
- セキュリティ機能
- バックアップ・復旧
無料プランがあり、学習や個人開発なら無料で十分かと思います。
7.まとめ
MongoDBの実装はAI(Gemini-CLI)にお任せだったので、
こうやって改めてアウトプットしながら理解を深めることは大事だなと感じました。
今後も理解を深めて現状の実装レベルのものを解読・実装出来るように、
今後も振り返りのアウトプットは続けていこうと思います。