Express.js でのビューエンジン:EJS、Pug、Handlebars など
Expressにおける「next」関数:詳細な解説
「next」関数の役割
次のミドルウェアへ進む:
- 現在のミドルウェアの処理が完了したら、
next()
関数を呼び出すことで、次のミドルウェアにリクエスト処理を移行します。 - すべてのミドルウェアが処理を終えると、最終的にはルーティングハンドラが呼び出され、レスポンスが送信されます。
- 現在のミドルウェアの処理が完了したら、
エラー処理:
- エラーが発生した場合、
next(error)
を呼び出すことで、エラーハンドリングミドルウェアに処理を移行できます。 - エラーハンドリングミドルウェアは、適切なエラーメッセージをクライアントに返したり、ログを記録したりすることができます。
- エラーが発生した場合、
app.use('/users', (req, res, next) => {
// 認証処理
if (!req.isAuthenticated) {
return next(new Error('未認証'));
}
// ユーザー情報の取得
User.findById(req.params.id, (err, user) => {
if (err) {
return next(err);
}
if (!user) {
return res.status(404).send('ユーザーが見つかりません');
}
// ユーザー情報の処理
res.json(user);
});
});
上記の例では、'/users'
パスへのリクエストに対して、以下の処理が行われます。
- 認証チェック: ユーザーが認証されていない場合は、
next(new Error('未認証'))
を呼び出して、エラーハンドリングミドルウェアに処理を移行します。 - ユーザー情報の取得: ユーザーが認証済みの場合は、
User.findById()
でユーザー情報を取得します。 - ユーザー情報が見つからない場合: ユーザー情報が見つからない場合は、ステータスコード 404 とともに適切なメッセージを送信します。
- ユーザー情報の処理: ユーザー情報が正常に取得できた場合は、JSON形式でレスポンスとして返します。
補足
next()
関数は、非同期に呼び出すことができます。next()
関数は、一度だけ呼び出す必要があります。複数回呼び出すと、予期しない動作が発生する可能性があります。- エラーハンドリングミドルウェアは、
app.use()
メソッドを使用して、他のミドルウェアよりも後に定義する必要があります。
サンプルコード:ユーザー認証と認可
ユーザーモデル
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
role: { type: String, enum: ['admin', 'user'], default: 'user' }
});
const User = mongoose.model('User', userSchema);
パスワードハッシュ化
const bcrypt = require('bcrypt');
const hashPassword = async (password) => {
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
return hashedPassword;
};
ユーザー登録
app.post('/register', async (req, res, next) => {
const { username, password } = req.body;
try {
const hashedPassword = await hashPassword(password);
const user = new User({ username, password: hashedPassword });
await user.save();
res.status(201).send('ユーザー登録が完了しました');
} catch (err) {
if (err.code === 11000) {
res.status(400).send('このユーザー名はすでに使用されています');
} else {
next(err);
}
}
});
ユーザーログイン
app.post('/login', async (req, res, next) => {
const { username, password } = req.body;
try {
const user = await User.findOne({ username });
if (!user) {
return res.status(401).send('ユーザー名またはパスワードが間違っています');
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(401).send('ユーザー名またはパスワードが間違っています');
}
req.session.userId = user._id;
res.status(200).send('ログインに成功しました');
} catch (err) {
next(err);
}
});
認可付きミドルウェア
const requireRole = (role) => {
return (req, res, next) => {
if (!req.session.userId) {
return res.status(401).send('ログインしてください');
}
User.findById(req.session.userId, (err, user) => {
if (err) {
return next(err);
}
if (user.role !== role) {
return res.status(403).send('この操作には十分な権限がありません');
}
next();
});
};
};
保護されたルート
app.get('/admin', requireRole('admin'), async (req, res) => {
// 管理者のみがアクセスできる処理
res.send('管理者ページ');
});
app.get('/user', requireRole('user'), async (req, res) => {
// ユーザーのみがアクセスできる処理
res.send('ユーザーページ');
});
このサンプルコードは、基本的な認証と認可の仕組みを示しています。実際のアプリケーションでは、より複雑なロジックを実装する必要があります。
- このコードは、Express、mongoose、bcrypt パッケージを使用しています。
- パスワードをハッシュ化して保存することで、セキュリティを強化しています。
requireRole
ミドルウェアを使用して、特定のロールを持つユーザーのみがアクセスできるルートを保護しています。
エラーハンドリングミドルウェアを使用する
- 最も一般的な方法は、エラーハンドリングミドルウェアを使用することです。
try-catch ブロックを使用する
- try-catch ブロックを使用して、エラー処理を行うこともできます。
- エラーが発生した場合は、
catch
ブロック内で適切な処理を行うことができます。
Promise を使用する
- 非同期処理を行う場合は、Promise を使用することができます。
それぞれの方法の比較
方法 | メリット | デメリット |
---|---|---|
エラーハンドリングミドルウェア | シンプルでわかりやすい | すべてのミドルウェアでエラー処理を行う必要がある |
try-catch ブロック | 特定のミドルウェアでのみエラー処理を行うことができる | コードが冗長になる可能性がある |
Promise | 非同期処理を簡単に扱える | コードが複雑になる可能性がある |
async/await | Promise よりも簡潔に書ける | コードが複雑になる可能性がある |
- シンプルなアプリケーションの場合は、エラーハンドリングミドルウェアを使用するのがおすすめです。
- 複雑なアプリケーションの場合は、try-catch ブロック、Promise、async/await のいずれかを使用することができます。
node.js express