Expressサーバーでの静的画像配信と認証フローの実装

静的リソース配信の仕組み

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アプリケーション基盤が構築できます。

タグ: express Node.js Mongoose MongoDB routing

6月14日 18:43 投稿