Express.js
Node.js用の高速でミニマリスト的なWebフレームワーク。シンプルで拡張性が高く、RESTful APIやWebアプリケーションサーバーの構築に広く使用される。
GitHub概要
expressjs/express
Fast, unopinionated, minimalist web framework for node.
スター67,354
ウォッチ1,696
フォーク19,481
作成日:2009年6月26日
言語:JavaScript
ライセンス:MIT License
トピックス
expressjavascriptnodejsserver
スター履歴
データ取得日時: 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設定が必要
主要リンク
- Express.js公式サイト
- Express.js公式ドキュメント
- Express.js GitHub リポジトリ
- Express.js ミドルウェア一覧
- Express Generator
- Express コミュニティ
書き方の例
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 });
});