Express.js

Node.js用の高速でミニマリスト的なWebフレームワーク。シンプルで拡張性が高く、RESTful APIやWebアプリケーションサーバーの構築に広く使用される。

JavaScriptNode.jsフレームワークWeb開発APIサーバーサイドミドルウェア

GitHub概要

expressjs/express

Fast, unopinionated, minimalist web framework for node.

スター67,354
ウォッチ1,696
フォーク19,481
作成日:2009年6月26日
言語:JavaScript
ライセンス:MIT License

トピックス

expressjavascriptnodejsserver

スター履歴

expressjs/express Star History
データ取得日時: 2025/7/17 10:32

フレームワーク

Express.js

概要

Express.jsは、Node.js用の最小限で柔軟なWebアプリケーションフレームワークです。Webサイトやモバイルアプリケーション向けの堅牢なAPIを迅速かつ簡単に開発できます。

詳細

Express.js(エクスプレス)は、2010年にTJ Holowaychukによって開発されたNode.js用のWebアプリケーションフレームワークです。「Node.jsのためのファスト、制約のない、最小主義的なWebフレームワーク」として設計されており、Web APIとWebアプリケーション開発のための強力な機能セットを提供します。ミドルウェアアーキテクチャにより、アプリケーションの構成を柔軟にカスタマイズでき、ルーティング、HTTPヘルパー、テンプレートエンジン統合、静的ファイル配信、エラーハンドリングなどの機能を提供します。軽量でありながら拡張性が高く、豊富なミドルウェア・プラグインエコシステムを持ち、RESTful API、マイクロサービス、フルスタックWebアプリケーション開発で広く採用されています。多くのNode.jsフレームワーク(Koa、Nest.js、Fastifyなど)の基盤となっており、Node.js Web開発のデファクトスタンダードの地位を確立しています。

メリット・デメリット

メリット

  • 軽量・高速: 最小限の構成で高いパフォーマンス
  • ミドルウェア・アーキテクチャ: 柔軟で拡張可能な構成
  • 豊富なエコシステム: 数千のミドルウェアとプラグイン
  • 学習コストの低さ: シンプルなAPIで習得しやすい
  • 高い自由度: 特定の設計パターンに縛られない
  • Node.js ネイティブ: Node.jsの非同期特性を活用
  • 大規模コミュニティ: 豊富なドキュメントとサポート
  • プロダクション実績: Netflix、Uber、WhatsAppなどで採用

デメリット

  • 規約不足: ベストプラクティスが明確でない場合がある
  • 設定の複雑さ: 大規模アプリケーションでは設定が煩雑
  • セキュリティ: セキュリティ設定を開発者に委ねる
  • エラーハンドリング: 非同期エラー処理に注意が必要
  • スケールの課題: 単一スレッドモデルの制約
  • TypeScript対応: 標準ではTypeScript設定が必要

主要リンク

書き方の例

Hello World

const express = require('express');
const app = express();
const port = 3000;

// 基本的なルート
app.get('/', (req, res) => {
  res.send('Hello, Express!');
});

// JSON レスポンス
app.get('/api/hello', (req, res) => {
  res.json({ message: 'Hello, World!', timestamp: new Date() });
});

// サーバー起動
app.listen(port, () => {
  console.log(`Express サーバーがポート ${port} で起動しました`);
});

// ES6 モジュール記法
import express from 'express';
const app = express();

app.get('/', (req, res) => {
  res.send('Hello from ES6 Express!');
});

export default app;

ルーティングとHTTPメソッド

const express = require('express');
const app = express();

// JSONパーサー設定
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// GET ルート
app.get('/users', (req, res) => {
  res.json([
    { id: 1, name: '田中太郎', email: '[email protected]' },
    { id: 2, name: '佐藤花子', email: '[email protected]' }
  ]);
});

// パラメータ付きルート
app.get('/users/:id', (req, res) => {
  const userId = parseInt(req.params.id);
  res.json({ id: userId, name: `ユーザー${userId}`, email: `user${userId}@example.com` });
});

// POST ルート
app.post('/users', (req, res) => {
  const { name, email } = req.body;
  const newUser = {
    id: Date.now(),
    name,
    email,
    createdAt: new Date()
  };
  res.status(201).json(newUser);
});

// PUT ルート
app.put('/users/:id', (req, res) => {
  const userId = req.params.id;
  const updateData = req.body;
  res.json({ id: userId, ...updateData, updatedAt: new Date() });
});

// DELETE ルート
app.delete('/users/:id', (req, res) => {
  const userId = req.params.id;
  res.json({ message: `ユーザー ${userId} を削除しました` });
});

// クエリパラメータの処理
app.get('/search', (req, res) => {
  const { q, page = 1, limit = 10 } = req.query;
  res.json({
    query: q,
    page: parseInt(page),
    limit: parseInt(limit),
    results: [`${q}の検索結果1`, `${q}の検索結果2`]
  });
});

ミドルウェアの使用

const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const helmet = require('helmet');
const app = express();

// セキュリティミドルウェア
app.use(helmet());

// CORS設定
app.use(cors({
  origin: ['http://localhost:3000', 'https://example.com'],
  credentials: true
}));

// ログ出力ミドルウェア
app.use(morgan('combined'));

// ボディパーサー
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

// 静的ファイル配信
app.use('/static', express.static('public'));

// カスタムミドルウェア(認証)
const authenticate = (req, res, next) => {
  const token = req.headers.authorization;
  if (!token) {
    return res.status(401).json({ error: '認証トークンが必要です' });
  }
  // トークン検証ロジック
  req.user = { id: 1, name: 'テストユーザー' };
  next();
};

// レート制限ミドルウェア
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分
  max: 100 // 最大100リクエスト
});
app.use('/api/', limiter);

// 保護されたルート
app.get('/api/profile', authenticate, (req, res) => {
  res.json({ user: req.user, message: '認証されたユーザーのプロフィール' });
});

// エラーハンドリングミドルウェア
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'サーバーエラーが発生しました' });
});

データベース統合(MongoDB)

const express = require('express');
const mongoose = require('mongoose');
const app = express();

// MongoDB接続
mongoose.connect('mongodb://localhost:27017/myapp', {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

// ユーザースキーマ定義
const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  createdAt: { type: Date, default: Date.now }
});

const User = mongoose.model('User', userSchema);

app.use(express.json());

// ユーザー一覧取得
app.get('/api/users', async (req, res) => {
  try {
    const { page = 1, limit = 10 } = req.query;
    const users = await User.find()
      .select('-password')
      .limit(limit * 1)
      .skip((page - 1) * limit)
      .sort({ createdAt: -1 });
    
    const total = await User.countDocuments();
    
    res.json({
      users,
      totalPages: Math.ceil(total / limit),
      currentPage: page
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// ユーザー作成
app.post('/api/users', async (req, res) => {
  try {
    const { name, email, password } = req.body;
    
    // バリデーション
    if (!name || !email || !password) {
      return res.status(400).json({ error: '必須フィールドが不足しています' });
    }
    
    const user = new User({ name, email, password });
    await user.save();
    
    const userResponse = user.toObject();
    delete userResponse.password;
    
    res.status(201).json(userResponse);
  } catch (error) {
    if (error.code === 11000) {
      res.status(400).json({ error: 'このメールアドレスは既に使用されています' });
    } else {
      res.status(500).json({ error: error.message });
    }
  }
});

// ユーザー更新
app.put('/api/users/:id', async (req, res) => {
  try {
    const { id } = req.params;
    const updateData = req.body;
    delete updateData.password; // パスワード更新は別エンドポイントで
    
    const user = await User.findByIdAndUpdate(
      id, 
      updateData, 
      { new: true, runValidators: true }
    ).select('-password');
    
    if (!user) {
      return res.status(404).json({ error: 'ユーザーが見つかりません' });
    }
    
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

認証とセッション管理

const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const session = require('express-session');
const MongoStore = require('connect-mongo');
const app = express();

app.use(express.json());

// セッション設定
app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  store: MongoStore.create({
    mongoUrl: 'mongodb://localhost:27017/myapp'
  }),
  cookie: {
    secure: false, // HTTPS環境では true
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000 // 24時間
  }
}));

// ユーザー登録
app.post('/api/auth/register', async (req, res) => {
  try {
    const { name, email, password } = req.body;
    
    // パスワードハッシュ化
    const saltRounds = 10;
    const hashedPassword = await bcrypt.hash(password, saltRounds);
    
    const user = new User({
      name,
      email,
      password: hashedPassword
    });
    
    await user.save();
    
    // JWTトークン生成
    const token = jwt.sign(
      { userId: user._id, email: user.email },
      'jwt-secret-key',
      { expiresIn: '7d' }
    );
    
    res.status(201).json({
      message: 'ユーザー登録が完了しました',
      token,
      user: {
        id: user._id,
        name: user.name,
        email: user.email
      }
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// ログイン
app.post('/api/auth/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    
    // ユーザー検索
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(401).json({ error: 'メールアドレスまたはパスワードが正しくありません' });
    }
    
    // パスワード検証
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      return res.status(401).json({ error: 'メールアドレスまたはパスワードが正しくありません' });
    }
    
    // セッションに保存
    req.session.userId = user._id;
    
    // JWTトークン生成
    const token = jwt.sign(
      { userId: user._id, email: user.email },
      'jwt-secret-key',
      { expiresIn: '7d' }
    );
    
    res.json({
      message: 'ログインしました',
      token,
      user: {
        id: user._id,
        name: user.name,
        email: user.email
      }
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// JWT認証ミドルウェア
const authenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'アクセストークンが必要です' });
  }
  
  jwt.verify(token, 'jwt-secret-key', (err, user) => {
    if (err) {
      return res.status(403).json({ error: '無効なトークンです' });
    }
    req.user = user;
    next();
  });
};

// 保護されたルート
app.get('/api/auth/profile', authenticateToken, async (req, res) => {
  try {
    const user = await User.findById(req.user.userId).select('-password');
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

ファイルアップロード処理

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();

// アップロードディレクトリ作成
const uploadDir = 'uploads';
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir);
}

// Multer設定
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, uploadDir);
  },
  filename: (req, file, cb) => {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
  }
});

// ファイルフィルター
const fileFilter = (req, file, cb) => {
  const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
  if (allowedTypes.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error('許可されていないファイル形式です'), false);
  }
};

const upload = multer({ 
  storage,
  fileFilter,
  limits: {
    fileSize: 5 * 1024 * 1024 // 5MB制限
  }
});

// 単一ファイルアップロード
app.post('/api/upload/single', upload.single('image'), (req, res) => {
  try {
    if (!req.file) {
      return res.status(400).json({ error: 'ファイルが選択されていません' });
    }
    
    res.json({
      message: 'ファイルアップロードが完了しました',
      file: {
        filename: req.file.filename,
        originalname: req.file.originalname,
        mimetype: req.file.mimetype,
        size: req.file.size,
        path: req.file.path
      }
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// 複数ファイルアップロード
app.post('/api/upload/multiple', upload.array('images', 5), (req, res) => {
  try {
    if (!req.files || req.files.length === 0) {
      return res.status(400).json({ error: 'ファイルが選択されていません' });
    }
    
    const fileInfo = req.files.map(file => ({
      filename: file.filename,
      originalname: file.originalname,
      mimetype: file.mimetype,
      size: file.size,
      path: file.path
    }));
    
    res.json({
      message: `${req.files.length}個のファイルアップロードが完了しました`,
      files: fileInfo
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// ファイル取得
app.get('/api/files/:filename', (req, res) => {
  const filename = req.params.filename;
  const filePath = path.join(uploadDir, filename);
  
  // ファイル存在確認
  if (!fs.existsSync(filePath)) {
    return res.status(404).json({ error: 'ファイルが見つかりません' });
  }
  
  res.sendFile(path.resolve(filePath));
});

// ファイル削除
app.delete('/api/files/:filename', (req, res) => {
  const filename = req.params.filename;
  const filePath = path.join(uploadDir, filename);
  
  if (!fs.existsSync(filePath)) {
    return res.status(404).json({ error: 'ファイルが見つかりません' });
  }
  
  fs.unlink(filePath, (err) => {
    if (err) {
      return res.status(500).json({ error: 'ファイル削除に失敗しました' });
    }
    res.json({ message: 'ファイルを削除しました' });
  });
});

// エラーハンドリング
app.use((error, req, res, next) => {
  if (error instanceof multer.MulterError) {
    if (error.code === 'LIMIT_FILE_SIZE') {
      return res.status(400).json({ error: 'ファイルサイズが大きすぎます' });
    }
  }
  res.status(500).json({ error: error.message });
});