Express.js
Node.jsベースの最小限で柔軟なWebアプリケーションフレームワーク。軽量で高速、豊富なミドルウェアエコシステムを提供。イベント駆動型アーキテクチャ。
アプリケーションサーバー
Express.js
概要
Express.jsは、Node.jsベースの最小限で柔軟なWebアプリケーションフレームワークです。「高速、非意見的、最小限」をモットーとし、Webアプリケーションやモバイルアプリケーション向けの堅牢な機能セットを提供します。Node.jsフレームワークで最も広く使用されており、1,800万以上のnpmパッケージエコシステムの恩恵を受けて、初心者から経験者まで幅広く選択されています。
詳細
Express.jsは2010年にリリースされ、Node.jsエコシステムにおけるWebフレームワークのデファクトスタンダードとなっています。軽量で柔軟性が高く、豊富なミドルウェアエコシステムを持つことから、RESTful APIの構築、Webアプリケーション開発、マイクロサービスアーキテクチャまで幅広い用途で採用されています。
主要な技術的特徴
- 最小限のコア: 必要最小限の機能のみを提供し、拡張性を重視
- 豊富なミドルウェア: ルーティング、認証、ログ、エラーハンドリングなど
- 高速性能: Node.jsのイベント駆動型アーキテクチャを活用
- 柔軟なルーティング: RESTful APIの構築に最適
- テンプレートエンジン: Pug、EJS、Handlebarsなど多様なテンプレートをサポート
用途
- RESTful API開発
- Webアプリケーション構築
- マイクロサービスアーキテクチャ
- リアルタイムアプリケーション
- GraphQLサーバー
- プロトタイプ開発
メリット・デメリット
メリット
- 学習コストの低さ: シンプルなAPIで習得しやすい
- 高いパフォーマンス: イベント駆動型で非同期処理に優秀
- 豊富なエコシステム: npmパッケージの恩恵を最大限活用
- 軽量: メモリ使用量が少なく、サーバーリソースを節約
- 活発なコミュニティ: 大規模なコミュニティによるサポート
- クロスプラットフォーム: Windows、Linux、macOSで動作
デメリット
- シングルスレッド: CPU集約的な処理には不向き
- コールバック地獄: 非同期処理の複雑さ(async/awaitで改善)
- 型安全性: JavaScriptの動的型付けによる実行時エラーのリスク
- 構造の自由度: プロジェクト構造の一貫性維持が困難
- セキュリティ: 適切なミドルウェア選択が重要
インストール・基本設定
前提条件
# Node.jsのインストール確認
node --version
npm --version
Express.jsのインストール
# 新しいプロジェクトの初期化
mkdir myapp
cd myapp
npm init -y
# Express.jsのインストール
npm install express
# 開発時に便利なツール
npm install --save-dev nodemon
基本的なアプリケーション設定
const express = require('express');
const app = express();
const port = 3000;
// ミドルウェアの設定
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 基本的なルート
app.get('/', (req, res) => {
res.send('Hello Express!');
});
// サーバー起動
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
設定例
環境設定ファイル
// config/config.js
module.exports = {
development: {
port: process.env.PORT || 3000,
database: 'mongodb://localhost:27017/myapp_dev',
jwt_secret: 'your-secret-key'
},
production: {
port: process.env.PORT || 80,
database: process.env.DATABASE_URL,
jwt_secret: process.env.JWT_SECRET
}
};
ミドルウェアの詳細設定
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
const app = express();
// セキュリティミドルウェア
app.use(helmet());
// CORS設定
app.use(cors({
origin: ['http://localhost:3000', 'https://yourdomain.com'],
credentials: true
}));
// ログ設定
app.use(morgan('combined'));
// レート制限
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 100, // 最大100リクエスト
message: 'Too many requests from this IP'
});
app.use('/api/', limiter);
// ボディパーサー
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 静的ファイル配信
app.use(express.static('public'));
RESTful APIの実装
const express = require('express');
const router = express.Router();
// ユーザー管理API
router.get('/users', async (req, res) => {
try {
const users = await User.find();
res.json(users);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.get('/users/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/users', async (req, res) => {
try {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
router.put('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
router.delete('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.status(204).send();
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
エラーハンドリング
// カスタムエラーミドルウェア
const errorHandler = (err, req, res, next) => {
console.error('Error:', err);
// Mongoose バリデーションエラー
if (err.name === 'ValidationError') {
const errors = Object.values(err.errors).map(e => e.message);
return res.status(400).json({
error: 'Validation Error',
details: errors
});
}
// MongoDB 重複エラー
if (err.code === 11000) {
return res.status(400).json({
error: 'Duplicate Entry',
field: Object.keys(err.keyPattern)[0]
});
}
// JWT エラー
if (err.name === 'JsonWebTokenError') {
return res.status(401).json({
error: 'Invalid token'
});
}
// デフォルトエラー
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error'
});
};
// 404ハンドラー
const notFound = (req, res, next) => {
res.status(404).json({
error: `Route ${req.originalUrl} not found`
});
};
// ミドルウェアとして追加
app.use(notFound);
app.use(errorHandler);
パフォーマンス最適化
クラスタリング
// cluster.js
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// CPUコア数分のワーカーを起動
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork(); // 新しいワーカーを起動
});
} else {
// ワーカープロセスでExpressアプリを実行
require('./app.js');
console.log(`Worker ${process.pid} started`);
}
圧縮とキャッシュ
const compression = require('compression');
const apicache = require('apicache');
// Gzip圧縮
app.use(compression());
// APIレスポンスキャッシュ
const cache = apicache.middleware;
app.use('/api/static-data', cache('5 minutes'));
// 条件付きキャッシュ
const onlyStatus200 = (req, res) => res.statusCode === 200;
app.use(cache('2 minutes', onlyStatus200));
接続プールとデータベース最適化
const mongoose = require('mongoose');
// MongoDB接続設定
mongoose.connect(process.env.DATABASE_URL, {
maxPoolSize: 10, // 最大接続数
serverSelectionTimeoutMS: 5000, // タイムアウト
socketTimeoutMS: 45000,
bufferCommands: false,
bufferMaxEntries: 0
});
// 接続イベント
mongoose.connection.on('connected', () => {
console.log('MongoDB connected');
});
mongoose.connection.on('error', (err) => {
console.error('MongoDB connection error:', err);
});
本番環境設定
PM2設定
// ecosystem.config.js
module.exports = {
apps: [{
name: 'myapp',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 80
},
log_file: './logs/combined.log',
out_file: './logs/out.log',
error_file: './logs/error.log',
time: true
}]
};
Docker設定
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# 依存関係のインストール
COPY package*.json ./
RUN npm ci --only=production
# アプリケーションファイルのコピー
COPY . .
# 非rootユーザーの作成
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nodejs
EXPOSE 3000
CMD ["node", "app.js"]
環境変数設定
# .env.production
NODE_ENV=production
PORT=3000
DATABASE_URL=mongodb://username:password@host:port/database
JWT_SECRET=your-super-secret-jwt-key
API_RATE_LIMIT_MAX=1000
REDIS_URL=redis://localhost:6379
セキュリティ対策
認証・認可の実装
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// JWT認証ミドルウェア
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.sendStatus(401);
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
};
// パスワードハッシュ化
const hashPassword = async (password) => {
const saltRounds = 12;
return await bcrypt.hash(password, saltRounds);
};
// ログイン処理
app.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign(
{ userId: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({ token });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
入力検証とサニタイゼーション
const { body, validationResult } = require('express-validator');
const validator = require('validator');
// バリデーションルール
const userValidationRules = () => {
return [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }).matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/),
body('name').trim().isLength({ min: 2, max: 50 }).escape()
];
};
// バリデーション結果チェック
const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
error: 'Validation failed',
details: errors.array()
});
}
next();
};
// 使用例
app.post('/users', userValidationRules(), validate, async (req, res) => {
// バリデーションが通過した場合の処理
});
モニタリング・ログ
ヘルスチェック
app.get('/health', (req, res) => {
const healthcheck = {
uptime: process.uptime(),
message: 'OK',
timestamp: Date.now(),
environment: process.env.NODE_ENV,
version: process.env.npm_package_version
};
res.status(200).json(healthcheck);
});
// 詳細ヘルスチェック
app.get('/health/detailed', async (req, res) => {
try {
// データベース接続チェック
await mongoose.connection.db.admin().ping();
const status = {
status: 'healthy',
timestamp: new Date().toISOString(),
services: {
database: 'connected',
memory: {
used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024)
},
uptime: process.uptime()
}
};
res.status(200).json(status);
} catch (error) {
res.status(503).json({
status: 'unhealthy',
error: error.message
});
}
});