静的リソース配信の仕組み
Webアプリケーションにおいて、クライアント側から画像などの静的ファイルを読み込む際は、サーバー側で適切なルーティング設定を行う必要があります。Expressフレームワークでは、ビルトインミドルウェアを活用した標準的な提供方法と、動的なパス解決を行うカスタムハンドラーの2つが一般的に用いられます。
クライアント側の画像参照
HTMLドキュメント内でメディアファイルを配置する際、サーバーの公開パスに対して適切なURLを指定します。以下は基本的なマークアップ例です。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>イメージ表示テスト</title>
</head>
<body>
<h2>コンテンツ読み込み完了</h2>
<div class="gallery">
<img src="/media/photo-01.jpg" alt="サンプル画像1">
<img src="/media/photo-02.jpg" alt="サンプル画像2">
</div>
</body>
</html>
サーバー側のルーティング設定
Expressではexpress.static()ミドルウェアを適用することで、指定ディレクトリ配下のファイルを自動的に公開できます。一方で、ファイル名を動的に解決したり、アクセス制御を細かく行ったりする場合は、app.get()でカスタムルートを実装するアプローチも有効です。
const express = require('express');
const path = require('path');
const app = express();
// 静的ファイルの標準配信設定
app.use('/public', express.static(path.join(__dirname, 'public')));
// 画像リクエスト用のカスタムルート
app.get('/media/:fileName', (req, res) => {
const { fileName } = req.params;
// ファイル名の形式検証(パス TRAVERSAL 攻撃の防止)
if (!/^[a-zA-Z0-9_\-]+\.(jpg|jpeg|png|gif|webp)$/i.test(fileName)) {
return res.status(400).send('無効なファイル名が指定されました');
}
const targetPath = path.resolve(__dirname, 'media', fileName);
res.sendFile(targetPath, (err) => {
if (err) {
res.status(404).send('指定された画像が見つかりません');
}
});
});
app.listen(3000, () => console.log('Server running on port 3000'));
認証システムとの統合
静的ファイル配信とユーザー認証機能を同一サーバーで運用する場合、データベース接続設定、モデル定義、そしてCRUD操作を含むエンドポイントを整理する必要があります。以下はMongoDBおよびMongooseを使用したメンバー管理と認証フローの実装例です。
データベース接続とスキーマ定義
const mongoose = require('mongoose');
// DB接続モジュール
const connectDB = async () => {
try {
await mongoose.connect('mongodb://127.0.0.1:27017/webapp_db', {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log('MongoDB接続確立');
} catch (err) {
console.error('接続エラー:', err);
}
};
// スキーマ設計
const memberSchema = new mongoose.Schema({
accountId: { type: String, required: true, unique: true },
passHash: { type: String, required: true },
createdAt: { type: Date, default: Date.now }
});
const MemberModel = mongoose.model('Members', memberSchema);
module.exports = { connectDB, MemberModel };
認証エンドポイントの実装
会員登録とログイン処理では、入力値のバリデーション、重複チェック、およびデータベースへのクエリ実行を非同期で処理します。テンプレートエンジンとしてEJSを使用し、エラー画面へ遷移する構造とします。
const express = require('express');
const path = require('path');
const { connectDB, MemberModel } = require('./db/connection');
const app = express();
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// クエリパラメータのパース設定
app.use(express.urlencoded({ extended: true }));
connectDB();
// 登録処理
app.get('/signup', async (req, res) => {
const { accountId, passHash } = req.query;
const validatePattern = /^[a-zA-Z0-9_]{6,16}$/;
if (!accountId || !passHash) {
return res.render('error', { message: 'IDとパスワードは必須です' });
}
if (!validatePattern.test(accountId) || !validatePattern.test(passHash)) {
return res.render('error', { message: '6〜16文字の英数・アンダースコアのみが使用可能です' });
}
try {
const exists = await MemberModel.findOne({ accountId });
if (exists) return res.render('error', { message: '既に登録されているアカウントです' });
await MemberModel.create({ accountId, passHash });
res.redirect('/login.html');
} catch (err) {
res.render('error', { message: '処理中にエラーが発生しました' });
}
});
// ログイン処理
app.get('/signin', async (req, res) => {
const { accountId, passHash } = req.query;
if (!accountId || !passHash) {
return res.render('error', { message: 'ログイン情報は必須です' });
}
try {
const user = await MemberModel.findOne({ accountId, passHash });
if (!user) return res.render('error', { message: 'アカウントが見つかりません' });
res.redirect('/dashboard');
} catch (err) {
res.render('error', { message: 'サーバーが一時的に利用できません' });
}
});
app.listen(3000);
上記の実装により、クライアントからのメディアリクエストを安全に処理しつつ、データベースを介したユーザー認証フローを同じNode.jsプロセス内で統合することが可能です。ミドルウェアの適用順序と非同期処理の制御を適切に行うことで、拡張性のあるWebアプリケーション基盤が構築できます。