404 Not Found

404 Not Found


nginx

「総合プロジェクト:タスク管理API(第1部)— プロジェクトの初期化とユーザー認証」

1. プロジェクト開始:アリスの初日

アリスと彼女のチームは、チーム向けタスク管理APIを構築するという新たな依頼を受けた。初日、彼らはまずプロジェクトのフレームワークとユーザー認証モジュールのセットアップから着手することにした。「認証はすべての基盤です」とアリスは言った。「認証がなければ、タスク管理システムの他の部分は意味をなさなくなります。」


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認証の仕組み

100%
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リクエストを送信し、返されたトークンを確認します。その後、そのトークンを使用して保護されたルートにアクセスし、認証が正常に機能していることを確認してください。

📖 まとめ

📝 練習問題

  1. ディレクトリ構造に従ってプロジェクトを初期化し、すべての依存関係をインストールし、npm run devがサーバーを起動できることを確認してください。
  2. UserTask の 2 つの Mongoose モデルを作成し、Postman を使用して登録およびログイン API をテストします。
  3. 認証ミドルウェアを作成し、/api/auth/me エンドポイントをテストします。トークンが指定されていない場合は 401 エラーを返し、有効なトークンが指定されている場合はユーザー情報を返すようにしてください。
  4. .env 内の JWT_SECRET および MONGO_URI を設定し、.gitignore 内の .env ファイルは無視してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%