MongoDB: Express/Mongoose統合:アプリケーションアーキテクチャ

最終更新:2026-08-26

ExpressとMongooseの統合は本格的なNode.jsアプリケーション開発の第一歩—マスターすれば堅牢なアプリケーションアーキテクチャを構築できます。

1. 学習内容


100%
graph TB
    Client[クライアント] -->|HTTP Request| Express[Express Server]
    
    subgraph "ミドルウェアスタック"
        Express --> MW1[認証ミドルウェア]
        MW1 --> MW2[バリデーション]
        MW2 --> MW3[エラーハンドリング]
    end
    
    MW3 --> Router[ルーター]
    Router --> Ctrl[コントローラー]
    Ctrl --> Model[Mongoose Model]
    Model --> DB[(MongoDB)]
    
    DB -->|データ| Model
    Model -->|JSON| Client

    style Express fill:#d4edda
    style Model fill:#cce5ff
    style DB fill:#fff3cd

2. プロジェクト構造規約

概念概要: 優れたプロジェクト構造は「関心の分離」原則に従う必要があります—ルーティング(URLマッピング)、コントローラー(リクエスト処理)、モデル(データ定義)が明確に分離され、相互に依存しすぎないようにします。これにより、各層を独立して変更・テストできます。

MVCパターンの適用: Express + Mongooseプロジェクトでは、MVCパターンを緩やかに適用します—Model(Mongooseスキーマ)がデータ構造と検証を担当、View(JSONレスポンス)がAPIデータを担当、Controller(リクエスト処理関数)がビジネスロジックを担当します。

ディレクトリ構造設計の原則:

ディレクトリ 責務 依存方向 テスト戦略
models/ スキーマ定義 + 検証 外部依存なし ユニットテスト
controllers/ リクエスト処理 + 調整 modelsに依存 統合テスト
routes/ URL → コントローラーマッピング controllers + middlewaresに依存 ルートテスト
middlewares/ 認証/認可/エラー処理 configに依存 ユニットテスト
validators/ joiスキーマ定義 外部依存なし ユニットテスト
utils/ ユーティリティ関数 外部依存なし ユニットテスト
BASH
shophub-api/
├── package.json
├── .env                    # 環境変数(非Git管理)
├── .env.example            # 環境変数テンプレート
├── src/
│   ├── app.js              # Expressアプリケーション
│   ├── config/
│   │   └── db.js           # Mongoose接続
│   ├── models/             # データモデル
│   │   ├── User.js
│   │   ├── Product.js
│   │   └── Review.js
│   ├── controllers/        # ビジネスロジック
│   │   ├── authController.js
│   │   ├── productController.js
│   │   └── reviewController.js
│   ├── routes/             # ルーティング
│   │   ├── auth.js
│   │   ├── products.js
│   │   └── reviews.js
│   ├── middlewares/        # ミドルウェア
│   │   ├── auth.js         # JWT認証
│   │   ├── errorHandler.js
│   │   └── validate.js
│   ├── validators/         # リクエスト検証
│   │   └── schemas.js
│   └── utils/              # ユーティリティ
│       ├── logger.js
│       └── jwt.js
└── README.md

ポイント解説:

  1. .envをGit管理から除外し、機密情報(DBパスワード、JWT鍵)を保護。
  2. models/は他のモジュールに依存せず、テストが容易。
  3. controllers/はリクエスト/レスポンス処理に集中し、ビジネスロジックはservices/に抽出可能。


3. 接続管理

概念説明: Mongoose接続はアプリケーションの「生命線」です。適切な接続管理には以下が含まれます:接続プールサイズの設定、接続タイムアウト、再接続戦略、エラーハンドリング、およびアプリケーションシャットダウン時の接続クローズ。これらを設定することで、高同時実行シナリオで接続を使い果たすことや、ネットワーク揺らぎによるサービス停止を防止できます。

接続プールの原理: Node.jsはシングルスレッドですが、データベース操作は非同期で実行されるため、複数のリクエストが同時にデータベースへの接続を必要とします。接続プールはあらかじめ一定数の接続を作成して保持し、リクエストごとに接続を確立するのではなく、プールから接続を「貸し出し」、使用後に「返却」します。接続プールサイズが小さすぎるとリクエストが待機し、大きすぎるとデータベースリソースを浪費します。

接続プールの動作:

100%
graph LR
    subgraph "Node.jsアプリケーション"
        R1[リクエスト1] -->|貸出| Pool[(接続プール<br/>min=5 max=50)]
        R2[リクエスト2] -->|貸出| Pool
        R3[リクエスト3] -->|待機| Queue[待機キュー<br/>waitQueueTimeoutMS]
    end
    
    Pool -->|アクティブ接続| Active[mongod Primary]
    Pool -->|アイドル接続| Idle[アイドル回収<br/>maxIdleTimeMS]
    R1 -.->|返却| Pool
    R3 -.->|返却接続を取得| Pool

    style Pool fill:#d4edda
    style Queue fill:#fff3cd
    style Active fill:#cce5ff

接続設定パラメータガイド:

パラメータ デフォルト値 チューニング戦略 高すぎるリスク 低すぎるリスク
maxPoolSize 100 ピーク同時接続数 × 1.5 DB接続を使い果たす リクエストキューがタイムアウト
minPoolSize 0 ピークの10%に設定 起動が遅い、メモリ使用量大 コールドスタート遅延
maxIdleTimeMS 0 30000(30秒) 頻繁に新規接続作成 接続リーク
waitQueueTimeoutMS 0 10000(10秒) リクエストバックログ リクエストが永久待機
serverSelectionTimeoutMS 30000 5000 長い遅延 高速失敗
connectTimeoutMS 30000 10000 接続確立が遅い 接続失敗

接続数計算式: maxPoolSize = ピーク同時接続数 * 平均クエリ時間(秒) * 安全係数(1.5)

JAVASCRIPT
// === Mongoose接続設定 ===
const mongoose = require('mongoose');

mongoose.connect(process.env.MONGODB_URI, {
  maxPoolSize: 50,          // 最大接続数(デフォルト100)
  minPoolSize: 5,           // 最小接続数(デフォルト0)
  maxIdleTimeMS: 30000,     // 接続アイドルタイムアウト(デフォルト無制限)
  waitQueueTimeoutMS: 10000, // 接続待機タイムアウト
  serverSelectionTimeoutMS: 5000, // サーバー選択タイムアウト
  socketTimeoutMS: 45000,   // ソケットタイムアウト
  family: 4                 // IPv4を使用
})
.then(() => console.log('MongoDB接続成功'))
.catch(err => console.error('MongoDB接続エラー:', err));

// === 接続イベント監視 ===
mongoose.connection.on('connected', () => {
  console.log('Mongooseが接続されました');
});

mongoose.connection.on('error', (err) => {
  console.error('Mongoose接続エラー:', err);
});

mongoose.connection.on('disconnected', () => {
  console.log('Mongoose接続が切断されました');
});

// === アプリケーション終了時に接続をクローズ ===
process.on('SIGINT', async () => {
  await mongoose.connection.close();
  console.log('MongoDB接続を閉じました');
  process.exit(0);
});


4. スキーマ設計のベストプラクティス

概念説明: Mongooseスキーマは単なる「データ構造定義」ではなく、検証ルール、インデックス、仮想フィールド、ミドルウェアを含む「データ契約」です。優れたスキーマ設計は、アプリケーション層でのデータ整合性を保証し、無効データがデータベースに到達するのを防ぎます。

スキーマ設計の決定事項:

設計上の決定 選択肢 推奨 理由
パスワード保存 平文 / ハッシュ passwordHash + select: false デフォルトクエリで返さず漏洩防止
タイムスタンプ 手動管理 / 自動 timestamps: true createdAt/updatedAtを自動管理
削除戦略 物理削除 / ソフト削除 isDeleted + pre-findフィルタ データ復旧可能、コンプライアンス要件
数値精度 Number / Decimal128 Decimal128(金額) 浮動小数点精度問題を回避
列挙値 文字列 / enum enum配列 + デフォルト 無効値を防止
一意制約 アプリ層 / DB層 unique: true + インデックス データベースレベルで一意性を保証

(1) 基本的なスキーマ定義

JAVASCRIPT
const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    required: [true, 'メールアドレスは必須です'],
    unique: true,
    lowercase: true,
    trim: true,
    match: [/^\S+@\S+\.\S+$/, '無効なメール形式です']
  },
  username: {
    type: String,
    required: true,
    unique: true,
    minlength: 3,
    maxlength: 30,
    match: [/^[a-zA-Z0-9_]+$/, '英数字とアンダースコアのみ']
  },
  passwordHash: {
    type: String,
    required: true,
    minlength: 60,  // bcryptハッシュ長
    select: false    // デフォルトで返さない
  },
  role: {
    type: String,
    enum: ['customer', 'admin', 'moderator'],
    default: 'customer'
  },
  isActive: {
    type: Boolean,
    default: true
  }
}, {
  timestamps: true,           // createdAt, updatedAtを自動管理
  toJSON: { virtuals: true }, // JSONシリアライズ時に仮想フィールドを含める
  toObject: { virtuals: true }
});

// インデックス作成
UserSchema.index({ email: 1 }, { unique: true });
UserSchema.index({ username: 1 }, { unique: true });

(2) ソフト削除パターン

概念説明: ソフト削除はドキュメントを実際に削除するのではなく、isDeletedフラグを設定します。これにより、データを復旧可能にし、誤削除やコンプライアンス要件(GDPRなど)に対応できます。Mongooseミドルウェアで全クエリにisDeleted: false条件を自動付与できます。

JAVASCRIPT
const ProductSchema = new mongoose.Schema({
  sku: { type: String, required: true, unique: true },
  title: { type: String, required: true },
  price: { type: Number, required: true },
  isDeleted: { type: Boolean, default: false, select: false }
}, { timestamps: true });

// 全findクエリで自動フィルタリング(ソフト削除を除外)
ProductSchema.pre(/^find/, function(next) {
  this.where({ isDeleted: { $ne: true } });
  next();
});

// 復元メソッド
ProductSchema.methods.restore = function() {
  this.isDeleted = false;
  return this.save();
};


5. ミドルウェアとフック

概念説明: Mongooseミドルウェアはスキーマレベルの「ライフサイクルフック」です。pre(実行前)とpost(実行後)で、保存・更新・削除・クエリ操作の前後にカスタムロジックを挿入できます。典型的なユースケース:パスワードハッシュ化、データ正規化、監査ログ、関連データのカスケード更新。

ミドルウェアタイプ:

タイプ フック 実行タイミング ユースケース
ドキュメント save, validate, remove モデルインスタンス操作前後 パスワードハッシュ、データ正規化
クエリ find, update, delete クエリ実行前後 ソフト削除フィルタ、監査ログ
集計 aggregate 集計パイプライン実行前後 ソフト削除フィルタ
モデル insertMany, bulkWrite バルク操作前後 バルクデータ検証
100%
sequenceDiagram
    participant App as アプリケーション
    participant Pre as pre-save<br/>ミドルウェア
    participant Model as Mongoose Model
    participant DB as MongoDB
    participant Post as post-save<br/>ミドルウェア

    App->>Pre: user.save()
    Pre->>Pre: パスワードをハッシュ化
    Pre->>Model: next()
    Model->>DB: 書き込み実行
    DB-->>Model: 成功
    Model->>Post: トリガー
    Post->>Post: 監査ログを記録
    Post-->>App: 返却

    style Pre fill:#cce5ff
    style Post fill:#d4edda

(1) パスワードハッシュ化(pre-save)

JAVASCRIPT
const bcrypt = require('bcrypt');

UserSchema.pre('save', async function(next) {
  // passwordHashが変更された場合のみハッシュ化
  if (!this.isModified('passwordHash')) return next();
  
  // パスワードをハッシュ化
  this.passwordHash = await bcrypt.hash(this.passwordHash, 10);
  next();
});

// パスワード検証メソッド
UserSchema.methods.comparePassword = function(candidate) {
  return bcrypt.compare(candidate, this.passwordHash);
};

(2) 更新タイムスタンプ(pre-update)

JAVASCRIPT
UserSchema.pre('findOneAndUpdate', function(next) {
  // updatedAtを自動更新
  this.set({ updatedAt: new Date() });
  next();
});

(3) カスケード削除(post-delete)

JAVASCRIPT
UserSchema.post('findOneAndDelete', async function(doc) {
  // ユーザー削除後、関連データをクリーンアップ
  if (doc) {
    await Review.deleteMany({ userId: doc._id });
    await Cart.deleteOne({ userId: doc._id });
  }
});


6. Expressルーティングとコントローラー

概念説明: ExpressルーティングはURLパスをコントローラー関数にマッピングします。コントローラーはリクエスト処理、データ検証、ビジネスロジック呼び出し、レスポンス返却を担当します。ルーティングをコントローラーから分離することで、URL構造の変更がビジネスロジックに影響せず、テストが容易になります。

RESTful API設計規約:

操作 HTTPメソッド パス 説明
一覧 GET /api/products ページネーション付き商品一覧
詳細 GET /api/products/:id 単一商品詳細
作成 POST /api/products 新規商品作成
更新 PUT /api/products/:id 商品全体更新
部分更新 PATCH /api/products/:id 商品部分更新
削除 DELETE /api/products/:id 商品削除
JAVASCRIPT
// === routes/products.js ===
const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');
const { authenticate, authorize } = require('../middlewares/auth');
const { validateProduct } = require('../middlewares/validate');

// 一般ユーザー向けルート
router.get('/', productController.listProducts);
router.get('/:id', productController.getProduct);

// 管理者専用ルート
router.post('/',
  authenticate,
  authorize('admin'),
  validateProduct,
  productController.createProduct
);

router.put('/:id',
  authenticate,
  authorize('admin'),
  validateProduct,
  productController.updateProduct
);

router.delete('/:id',
  authenticate,
  authorize('admin'),
  productController.deleteProduct
);

module.exports = router;
JAVASCRIPT
// === controllers/productController.js ===
const Product = require('../models/Product');

exports.listProducts = async (req, res) => {
  const { page = 1, limit = 20, category } = req.query;

  const query = { isActive: true };
  if (category) query.category = category;

  const [products, total] = await Promise.all([
    Product.find(query)
      .select('sku title price thumbnail rating')
      .sort({ createdAt: -1 })
      .limit(+limit)
      .skip((page - 1) * limit)
      .lean(),
    Product.countDocuments(query)
  ]);

  res.json({
    success: true,
    data: products,
    meta: { page: +page, limit: +limit, total }
  });
};

exports.getProduct = async (req, res) => {
  const product = await Product.findById(req.params.id);
  if (!product) {
    return res.status(404).json({ error: '商品が見つかりません' });
  }
  res.json(product);
};

exports.createProduct = async (req, res) => {
  const product = await Product.create(req.body);
  res.status(201).json(product);
};

exports.updateProduct = async (req, res) => {
  const product = await Product.findByIdAndUpdate(
    req.params.id,
    req.body,
    { new: true, runValidators: true }
  );
  if (!product) {
    return res.status(404).json({ error: '商品が見つかりません' });
  }
  res.json(product);
};

exports.deleteProduct = async (req, res) => {
  const product = await Product.findByIdAndDelete(req.params.id);
  if (!product) {
    return res.status(404).json({ error: '商品が見つかりません' });
  }
  res.json({ message: '商品を削除しました' });
};


7. エラーハンドリング

概念概要: API開発では、エラーは「例外」ではなく「常態」です。適切なエラーハンドリング戦略は、ユーザーに有意義なフィードバックを提供し、開発者が問題を迅速に診断でき、システムが過度な情報を漏洩しないことを保証します。

エラーハンドリングのベストプラクティス:

エラータイプ HTTPステータス ユーザーへのフィードバック ログ記録
検証エラー 400 「入力値が無効です」 警告レベル
認証エラー 401 「ログインしてください」 情報レベル
認可エラー 403 「権限がありません」 警告レベル
リソースなし 404 「リソースが見つかりません」 情報レベル
競合エラー 409 「データが既に存在します」 情報レベル
サーバーエラー 500 「サーバーエラーが発生しました」 エラーレベル + スタックトレース
JAVASCRIPT
// === middlewares/errorHandler.js ===
const errorHandler = (err, req, res, next) => {
  console.error('エラー:', err);

  // Mongoose検証エラー
  if (err.name === 'ValidationError') {
    const messages = Object.values(err.errors).map(e => e.message);
    return res.status(400).json({
      error: '検証エラー',
      details: messages
    });
  }

  // MongoDB重複キーエラー
  if (err.code === 11000) {
    const field = Object.keys(err.keyValue)[0];
    return res.status(409).json({
      error: `${field}は既に使用されています`
    });
  }

  // JWTエラー
  if (err.name === 'JsonWebTokenError') {
    return res.status(401).json({ error: '無効なトークンです' });
  }

  // デフォルトサーバーエラー
  res.status(500).json({
    error: 'サーバーエラーが発生しました'
  });
};

module.exports = errorHandler;
JAVASCRIPT
// === app.js ===
const errorHandler = require('./middlewares/errorHandler');

// ルーティングの後にエラーハンドラを登録
app.use(errorHandler);

8. 実践サンプル

▶ サンプル 1:Expressアプリケーション初期化(難易度 ⭐)

JAVASCRIPT
// app.js - Expressアプリケーションのエントリーポイント
const express = require('express');
const mongoose = require('mongoose');
const errorHandler = require('./middlewares/errorHandler');

const app = express();

// ミドルウェア設定
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// データベース接続
const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI, {
      maxPoolSize: 50,
      minPoolSize: 5,
      serverSelectionTimeoutMS: 5000
    });
    console.log('MongoDB接続成功');
  } catch (error) {
    console.error('MongoDB接続エラー:', error.message);
    process.exit(1);
  }
};

// 接続イベント監視
mongoose.connection.on('disconnected', () => {
  console.log('MongoDB接続が切断されました');
});

// ルーティング
app.use('/api/products', require('./routes/products'));
app.use('/api/users', require('./routes/users'));

// ヘルスチェック
app.get('/health', (req, res) => {
  const dbStatus = mongoose.connection.readyState === 1 ? 'connected' : 'disconnected';
  res.json({ status: 'ok', database: dbStatus });
});

// エラーハンドラ(最後に配置)
app.use(errorHandler);

// グレースフルシャットダウン
process.on('SIGINT', async () => {
  await mongoose.connection.close();
  console.log('MongoDB接続を閉じました');
  process.exit(0);
});

// 起動
const PORT = process.env.PORT || 3000;
connectDB().then(() => {
  app.listen(PORT, () => console.log(`サーバー起動: http://localhost:${PORT}`));
});

module.exports = app;

出力:

TEXT 📖 参照専用
Expressアプリケーションの基本構成:Mongoose接続管理、ルーティング、エラーハンドリング、グレースフルシャットダウンを含む。

▶ サンプル 2:完全なCRUD API実装(難易度 ⭐⭐)

JAVASCRIPT
// controllers/productController.js - 商品CRUDコントローラー
const Product = require('../models/Product');
const mongoose = require('mongoose');

// 商品一覧(ページネーション + フィルタ)
exports.listProducts = async (req, res, next) => {
  try {
    const { page = 1, limit = 20, category, minPrice, maxPrice } = req.query;

    const query = { isActive: true };
    if (category) query.category = category;
    if (minPrice || maxPrice) {
      query.price = {};
      if (minPrice) query.price.$gte = +minPrice;
      if (maxPrice) query.price.$lte = +maxPrice;
    }

    const [products, total] = await Promise.all([
      Product.find(query)
        .select('sku title price category avgRating')
        .sort({ createdAt: -1 })
        .limit(+limit)
        .skip((+page - 1) * +limit)
        .lean(),
      Product.countDocuments(query)
    ]);

    res.json({
      success: true,
      data: products,
      meta: { page: +page, limit: +limit, total, pages: Math.ceil(total / +limit) }
    });
  } catch (error) {
    next(error);
  }
};

// 商品詳細
exports.getProduct = async (req, res, next) => {
  try {
    const { id } = req.params;

    if (!mongoose.Types.ObjectId.isValid(id)) {
      return res.status(400).json({ success: false, error: '無効なID形式です' });
    }

    const product = await Product.findById(id);
    if (!product || !product.isActive) {
      return res.status(404).json({ success: false, error: '商品が見つかりません' });
    }

    res.json({ success: true, data: product });
  } catch (error) {
    next(error);
  }
};

// 商品作成
exports.createProduct = async (req, res, next) => {
  try {
    const { sku, title, price, category, description, stock } = req.body;

    // SKU重複チェック
    const existing = await Product.findOne({ sku });
    if (existing) {
      return res.status(409).json({ success: false, error: `SKU ${sku}は既に存在します` });
    }

    const product = await Product.create({ sku, title, price, category, description, stock });
    res.status(201).json({ success: true, data: product });
  } catch (error) {
    next(error);
  }
};

// 商品更新
exports.updateProduct = async (req, res, next) => {
  try {
    const { id } = req.params;
    const updates = req.body;

    // 更新禁止フィールドを除外
    delete updates._id;
    delete updates.createdAt;
    delete updates.__v;

    const product = await Product.findByIdAndUpdate(
      id,
      { $set: updates },
      { new: true, runValidators: true }
    );

    if (!product) {
      return res.status(404).json({ success: false, error: '商品が見つかりません' });
    }

    res.json({ success: true, data: product });
  } catch (error) {
    next(error);
  }
};

// 商品削除(ソフト削除)
exports.deleteProduct = async (req, res, next) => {
  try {
    const { id } = req.params;

    const product = await Product.findByIdAndUpdate(
      id,
      { $set: { isDeleted: true } },
      { new: true }
    );

    if (!product) {
      return res.status(404).json({ success: false, error: '商品が見つかりません' });
    }

    res.status(204).send();
  } catch (error) {
    next(error);
  }
};

出力:

TEXT 📖 参照専用
商品CRUD API:一覧(ページネーション)、詳細、作成(重複チェック)、更新(禁止フィールド除外)、削除(ソフト削除)。

▶ サンプル 3:認証ミドルウェアとルーティング(難易度 ⭐⭐⭐)

JAVASCRIPT
// middlewares/auth.js - JWT認証ミドルウェア
const jwt = require('jsonwebtoken');

// JWT認証
exports.authenticate = (req, res, next) => {
  const authHeader = req.header('Authorization');

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: '認証が必要です' });
  }

  const token = authHeader.replace('Bearer ', '');

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;  // { id, role, username }
    next();
  } catch (err) {
    return res.status(401).json({ error: '無効なトークンです' });
  }
};

// 権限チェック
exports.authorize = (...roles) => {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: '認証が必要です' });
    }

    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'この操作を行う権限がありません' });
    }

    next();
  };
};

// routes/products.js - 商品ルーティング
const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');
const { authenticate, authorize } = require('../middlewares/auth');

// 公開ルート(認証不要)
router.get('/', productController.listProducts);
router.get('/:id', productController.getProduct);

// 管理者専用ルート
router.post('/',
  authenticate,
  authorize('admin'),
  productController.createProduct
);

router.put('/:id',
  authenticate,
  authorize('admin'),
  productController.updateProduct
);

router.delete('/:id',
  authenticate,
  authorize('admin'),
  productController.deleteProduct
);

module.exports = router;

// テストコマンド
// 商品一覧: curl http://localhost:3000/api/products
// 商品作成(管理者): curl -X POST http://localhost:3000/api/products \
//   -H "Authorization: Bearer <token>" \
//   -H "Content-Type: application/json" \
//   -d '{"sku":"TEST-001","title":"Test Product","price":99,"category":"Electronics"}'

出力:

TEXT 📖 参照専用
JWT認証ミドルウェア(authenticate)と権限チェック(authorize)。商品APIは一般公開(一覧・詳細)と管理者専用(作成・更新・削除)に分離。

❓ よくある質問

Q Mongoose接続はどこで初期化すべき?
A config/db.jsにカプセル化し、アプリケーション起動時に一度だけ接続。
Q select: falseの目的は?
A パスワードなどの機密フィールドをデフォルトクエリで返さないようにし、漏洩を防止。
Q ExpressアプリケーションでMongooseミドルウェアを使用すべき?
A スキーマに関連するロジック(ハッシュ化、検証)はMongooseミドルウェアを使用し、リクエスト処理ロジックはExpressミドルウェアを使用。

📖 まとめ


📝 練習問題

  1. 基礎問題(⭐): Mongoose接続管理モジュールを作成(接続プール設定 + イベント監視)。
  2. 基礎問題(⭐): Userスキーマを設計(メール検証 + パスワードハッシュ + タイムスタンプ)。
  3. 応用問題(⭐⭐): 商品CRUD APIを実装(ルーティング + コントローラー + エラーハンドリング)。
  4. 応用問題(⭐⭐): ソフト削除パターンを実装(pre-findフィルタ + 復元メソッド)。
  5. チャレンジ(⭐⭐⭐): 完全なExpressアプリケーションを構築(認証 + ルーティング + エラーハンドリング + 接続管理)。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%