「総合プロジェクト:タスク管理API(第1部)— プロジェクトの初期化とユーザー認証」
1. プロジェクト開始:アリスの初日
アリスと彼女のチームは、チーム向けタスク管理APIを構築するという新たな依頼を受けた。初日、彼らはまずプロジェクトのフレームワークとユーザー認証モジュールのセットアップから着手することにした。「認証はすべての基盤です」とアリスは言った。「認証がなければ、タスク管理システムの他の部分は意味をなさなくなります。」
- プロジェクトの立ち上げとディレクトリ構造の設計は、エンジニアリングにおける最初のステップです。
- データ層の基盤となるのは、Mongooseのモデル設計である
- JWTとbcryptの組み合わせは、Node.jsにおける認証の主流のソリューションです
- 登録/ログイン API は、認証モジュールの 2 つの主要なエンドポイントで構成されています
authミドルウェアは、認証を必要とするルーティングを保護します
2. プロジェクトの初期化とディレクトリ構造
(1) Expressプロジェクトの初期化
BASH
mkdir task-manager-api && cd task-manager-api
npm init -y
npm install express mongoose bcryptjs jsonwebtoken dotenv cors helmet
npm install --save-dev nodemon
(2) ディレクトリ構造の設計
TEXT
task-manager-api/
├── src/
│ ├── config/
│ │ └── db.js
│ ├── middleware/
│ │ └── auth.js
│ ├── models/
│ │ ├── User.js
│ │ └── Task.js
│ ├── routes/
│ │ ├── auth.js
│ │ └── tasks.js
│ ├── validators/
│ │ └── authValidator.js
│ └── app.js
├── .env
├── .gitignore
├── package.json
└── server.js
| ディレクトリ/ファイル | 説明 |
|---|---|
src/config/ |
データベース接続、環境変数、およびその他の設定 |
src/middleware/ |
認証やエラー処理などのためのミドルウェア |
src/models/ |
Mongooseのスキーマおよびモデル定義 |
src/routes/ |
機能別ルーティングモジュール |
src/validators/ |
リクエストパラメータの検証ロジック |
src/app.js |
Express メインアプリケーションファイル |
server.js |
入力ファイル、サーバーを起動 |
▶ 例:server.js エントリファイル
JAVASCRIPT
require('dotenv').config();
const app = require('./src/app');
const connectDB = require('./src/config/db');
connectDB();
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
▶ 例:データベース接続の設定
JAVASCRIPT
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
console.log('MongoDB connected');
} catch (err) {
console.error('MongoDB connection error:', err.message);
process.exit(1);
}
};
module.exports = connectDB;
3. Mongooseモデルの設計
(1) ユーザーモデル
| フィールド | タイプ | 必須 | 説明 |
|---|---|---|---|
username |
文字列 | はい | ユーザー名、一意インデックス |
email |
文字列 | はい | メールアドレス、一意インデックス |
password |
文字列 | はい | bcryptでハッシュ化されたパスワード |
role |
文字列 | いいえ | 役割:user(デフォルト) / admin |
createdAt |
日付 | 自動 | 作成日 |
(2) タスクモデル
| フィールド | タイプ | 必須 | 説明 |
|---|---|---|---|
title |
文字列 | はい | タスク名 |
description |
文字列 | いいえ | タスクの詳細 |
status |
文字列 | なし | pending (既定値) / in-progress / completed |
priority |
文字列 | いいえ | low (既定値) / medium / high |
assignedTo |
ObjectId | はい | 割り当てられたユーザー、ユーザーに関連付けられている |
dueDate |
日付 | 番号 | 締切 |
createdAt |
日付 | 自動 | 作成日 |
updatedAt |
日付 | 自動 | 最終更新日 |
▶ 例:ユーザーモデルの定義
JAVASCRIPT
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
password: { type: String, required: true, minlength: 6 },
role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
userSchema.methods.comparePassword = function (candidate) {
return bcrypt.compare(candidate, this.password);
};
module.exports = mongoose.model('User', userSchema);
▶ 例:タスクモデルの定義
JAVASCRIPT
const mongoose = require('mongoose');
const taskSchema = new mongoose.Schema({
title: { type: String, required: true, trim: true },
description: { type: String, default: '' },
status: { type: String, enum: ['pending', 'in-progress', 'completed'], default: 'pending' },
priority: { type: String, enum: ['low', 'medium', 'high'], default: 'low' },
assignedTo: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
dueDate: { type: Date }
}, { timestamps: true });
module.exports = mongoose.model('Task', taskSchema);
4. ユーザー認証モジュール
(1) JWT認証の仕組み
graph TD
A[User Registration/Log In] --> B[Server Authentication Credentials]
B --> C[Generate JWT Token]
C --> D[Back Token To the client]
D --> E[Client-side Token Request]
E --> F[auth Middleware Validation Token]
F -->|Valid| G[Forward to the routing processor]
F -->|Invalid| H[Back 401 Error]
(2) 認証APIエンドポイントの設計
| メソッド | パス | 説明 | 認証が必要 |
|---|---|---|---|
| 投稿 | /api/auth/register |
ユーザー登録 | いいえ |
| 投稿 | /api/auth/login |
ユーザーログイン | いいえ |
| GET | /api/auth/me |
現在のユーザーを取得 | はい |
▶ 例:登録およびログインのルート
JAVASCRIPT
const router = require('express').Router();
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const generateToken = (id) => jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '7d' });
router.post('/register', async (req, res, next) => {
try {
const { username, email, password } = req.body;
const user = await User.create({ username, email, password });
res.status(201).json({ token: generateToken(user._id), user: { id: user._id, username, email, role: user.role } });
} catch (err) {
next(err);
}
});
router.post('/login', async (req, res, next) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await user.comparePassword(password))) {
return res.status(401).json({ message: 'Invalid credentials' });
}
res.json({ token: generateToken(user._id), user: { id: user._id, username: user.username, email, role: user.role } });
} catch (err) {
next(err);
}
});
module.exports = router;
▶ 例:認証ミドルウェア
JAVASCRIPT
const jwt = require('jsonwebtoken');
const User = require('../models/User');
module.exports = async (req, res, next) => {
const header = req.headers.authorization;
if (!header || !header.startsWith('Bearer ')) {
return res.status(401).json({ message: 'No token provided' });
}
try {
const decoded = jwt.verify(header.split(' ')[1], process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select('-password');
if (!req.user) return res.status(401).json({ message: 'User not found' });
next();
} catch {
res.status(401).json({ message: 'Invalid token' });
}
};
▶ 例:現在のユーザーのルートを取得する
JAVASCRIPT
const auth = require('../middleware/auth');
router.get('/me', auth, async (req, res) => {
res.json({ user: { id: req.user._id, username: req.user.username, email: req.user.email, role: req.user.role } });
});
5. app.js メインファイルと .env 設定
▶ 例:app.js の統合
JAVASCRIPT
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const authRoutes = require('./routes/auth');
const app = express();
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use('/api/auth', authRoutes);
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.statusCode || 500).json({ message: err.message || 'Server Error' });
});
module.exports = app;
▶ 例:.env 環境変数
TEXT
PORT=3000
MONGO_URI=mongodb://localhost:27017/task-manager
JWT_SECRET=your_super_secret_key_change_in_production
6. 包括的な例:プロジェクト初期化プロセスの全容
以下のコードは、前述のすべてのコアモジュールを統合したものです。約80行のコアコードで、認証機能を備えた基本的なタスク管理APIを実行することができます:
JAVASCRIPT
// server.js
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const helmet = require('helmet');
const app = express();
app.use(helmet(), cors(), express.json());
// --- Models ---
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });
userSchema.pre('save', async function () { if (this.isModified('password')) this.password = await bcrypt.hash(this.password, 10); });
userSchema.methods.comparePassword = function (pw) { return bcrypt.compare(pw, this.password); };
const User = mongoose.model('User', userSchema);
// --- Auth Middleware ---
const auth = async (req, res, next) => {
try {
const decoded = jwt.verify(req.headers.authorization?.split(' ')[1], process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select('-password');
next();
} catch { res.status(401).json({ message: 'Unauthorized' }); }
};
// --- Routes ---
app.post('/api/auth/register', async (req, res) => {
const user = await User.create(req.body);
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.status(201).json({ token, user: { id: user._id, username: user.username, role: user.role } });
});
app.post('/api/auth/login', async (req, res) => {
const user = await User.findOne({ email: req.body.email });
if (!user || !(await user.comparePassword(req.body.password))) return res.status(401).json({ message: 'Invalid credentials' });
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.json({ token, user: { id: user._id, username: user.username, role: user.role } });
});
app.get('/api/auth/me', auth, (req, res) => res.json(req.user));
// --- Start ---
mongoose.connect(process.env.MONGO_URI).then(() => {
app.listen(process.env.PORT || 3000, () => console.log('Server running'));
});
❓ よくある質問
Q プロジェクトのディレクトリ構造はどのように設計すべきですか?
A 機能ごとに整理します。データモデルは
models/ に、ルートは routes/ に、ミドルウェアは middleware/ に、設定ファイルは config/ に格納します。エントリポイントである app.js はルートディレクトリに配置します。Q なぜネイティブドライバーではなくMongooseを使うのですか?
A Mongooseはスキーマ検証、ミドルウェアフック、型ヒント、クエリビルダーを提供しており、開発効率を高めます。一方、ネイティブドライバーは軽量で、単純なシナリオに適しています。
Q .env ファイルは Git にコミットすべきですか?
A 絶対にコミットしてはいけません。.env ファイルにはデータベースのパスワードやキーなどの機密情報が含まれているため、.gitignore に追加し、デプロイ時には環境変数や CI を通じて設定する必要があります。
Q JWTのシークレットはどのくらいの長さにするべきですか?
A 少なくとも32文字のランダムな文字列にする必要があります。本番環境では、64文字以上を推奨します。
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))" を使用して生成することができます。Q 登録APIをテストするにはどうすればよいですか?
A Postmanまたはcurlを使用して、/api/auth/register に対してPOSTリクエストを送信し、返されたトークンを確認します。その後、そのトークンを使用して保護されたルートにアクセスし、認証が正常に機能していることを確認してください。
- Q: なぜ最初に認証を行う必要があるのですか? A: 認証はビジネスロジックを実行するための前提条件です。身元が確認できなければ、データの所有権を確立したりアクセス権限を管理したりすることは不可能であり、その後のすべてのCRUD操作は認証に依存しています。
- Q: パスワードの保存は安全ですか? A: bcryptはソルトを用いた適応型ハッシュ方式を採用しており、平文やMD5/SHAよりもはるかに優れています。10ラウンドの場合、1回の処理にかかる時間は約100ミリ秒で、セキュリティとパフォーマンスのバランスが取れています。
- Q: JWTのシークレットはどのように管理すべきですか? A: 開発環境では.envファイルを使用し、本番環境では環境変数またはシークレット管理サービス(AWS Secrets Managerなど)を使用してください。決してハードコーディングしないでください。
- Q: 登録・ログインのテストはどのように行えばよいですか? A: Postman または curl を使用して、
/api/auth/registerおよび/api/auth/loginに対して POST リクエストを送信し、返されたトークンを確認してください。 - Q: プロジェクトの構造はどのように構成すべきですか? A: 関心の分離を維持するために、MVCコンポーネント(モデル(データ)、ルート、ミドルウェア、設定)に分解してください。
- Q: bcryptとcrypto、どちらが優れていますか? A: bcryptはパスワード専用に設計されており、組み込みのソルト値と適応型コストを備えているため、cryptoパッケージに含まれるSHAシリーズよりもパスワードのハッシュ化に適しています。
📖 まとめ
- プロジェクトの立ち上げ:重要な概念と、初日からAliceを活用する方法
- プロジェクトの初期化とディレクトリ構造の基本概念と使い方
- Mongooseモデル設計の基本概念と使い方
- ユーザー認証モジュールの基本概念と使用方法
- app.js メインファイルと .env 設定ファイルの基本概念と使い方
- 包括的な例:プロジェクト初期化プロセス全体の基本概念と使用方法
📝 練習問題
- ディレクトリ構造に従ってプロジェクトを初期化し、すべての依存関係をインストールし、
npm run devがサーバーを起動できることを確認してください。 UserとTaskの 2 つの Mongoose モデルを作成し、Postman を使用して登録およびログイン API をテストします。- 認証ミドルウェアを作成し、
/api/auth/meエンドポイントをテストします。トークンが指定されていない場合は 401 エラーを返し、有効なトークンが指定されている場合はユーザー情報を返すようにしてください。 .env内の JWT_SECRET および MONGO_URI を設定し、.gitignore内の.envファイルは無視してください。



