Express.js 入門から運用まで:概要・基本概念・実践ベストプラクティス

Express.js とは — 概要

Express.js(以下 Express)は、Node.js 上で動作する最も広く使われている軽量なWebアプリケーションフレームワークです。シンプルなAPIでルーティングやミドルウェアの仕組みを提供し、REST API やシングルページアプリケーション(SPA)のバックエンド、プロトタイプやマイクロサービスの構築など幅広い用途で採用されています。設計は「最小限のコア + 豊富なミドルウェアエコシステム」による拡張性を重視しています。

歴史と位置づけ

Express は TJ Holowaychuk によって開発され、2010年代初頭から Node.js コミュニティで急速に普及しました。軽量で学習コストが低く、既存の Node.js の HTTP モジュールを抽象化して扱いやすくする点が受け入れられ、事実上のデファクトスタンダードとなりました。コアは小さく、必要な機能はミドルウェアとして追加する設計のため、用途に応じた柔軟な構成が可能です。

基本的な概念

  • アプリケーション(app): express() が返すオブジェクト。ルート登録やミドルウェアの適用、サーバ起動などの操作を行う。
  • ルーティング: HTTP メソッドとパスに基づいて処理を振り分ける仕組み。app.get(), app.post() などで定義する。
  • ミドルウェア: リクエスト・レスポンスの間に入る関数。認証、ログ、ボディ解析、エラーハンドリングなどを行う。next() を呼ぶことで次のミドルウェアへ制御を渡す。
  • Router: モジュール化されたルータ。複数のルートやミドルウェアをまとでて管理し、アプリにマウントできる。

主要な機能とよく使われる API

代表的な機能とポイントは次の通りです。

  • ルーティング: パラメータ、クエリ、ワイルドカードなどを使った柔軟なルート設計が可能。
  • ミドルウェアチェーン: リクエストに対して複数の関数を順に適用でき、処理を分割しやすい。
  • 静的ファイル配信: express.static で静的資産を配信。
  • テンプレートエンジン: Pug、EJS、Handlebars 等と連携してサーバサイドレンダリングが可能。
  • ボディパーサー: かつては外部モジュール(body-parser)が一般的でしたが、Express 4.16.0 以降は express.json(), express.urlencoded() が組み込まれています。
  • エラーハンドリング: 引数が4つ(err, req, res, next)のミドルウェアで集中処理可能。

簡単なサンプルコード

典型的な Express アプリの例(最小構成):

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

// 組み込みのボディパーサー
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// ログ(開発用)
const morgan = require('morgan');
app.use(morgan('dev'));

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

// モジュール化されたルータの例
const userRouter = require('./routes/users');
app.use('/users', userRouter);

// エラーハンドラ(4引数)
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: '内部エラー' });
});

app.listen(3000, () => console.log('Server started on :3000'));

実践的な使い方とベストプラクティス

  • ミドルウェアの順序: ミドルウェアは登録順に実行されるため、認証/認可、ボディ解析、ログなどの順序を意識して配置する。
  • ルーティングの分割: express.Router() を使って機能ごとにルートを分割し、保守性を高める。
  • 非同期処理: async/await を使う場合は、Promise を返す関数のエラーを次のエラーハンドラに渡す仕組み(try/catch で next(err))を忘れない。Express 5 以降は async ハンドラのエラー処理が改善される予定だが、現行の 4.x 系では明示的な扱いが必要。
  • 入力検証: express-validator などでリクエストのバリデーションを行い、攻撃や不正データを防ぐ。
  • 構成管理: 環境変数(dotenv 等)を利用し、設定はコードから切り離す。

セキュリティと運用

Express 自体は軽量で機能は最小限のため、セキュリティや運用はミドルウェアや周辺ツールで補う必要があります。主な対策は以下の通りです。

  • HTTPヘッダによる保護: helmet を導入して一般的なヘッダを設定する。
  • CORS: cors ミドルウェアで適切なオリジン制御を行う。
  • レート制限: express-rate-limit 等でブルートフォースやDoS対策を行う。
  • ログと監視: morgan でアクセスログ、winston や pino でアプリログを収集し、監視(Prometheus, ELK 等)と連携する。
  • プロセスマネージャ: PM2 や systemd を使ってプロセス管理とクラスタリング、再起動を行う。
  • プロキシ設定: 本番では Nginx やロードバランサの背後に置き、TLS終端や静的配信、HTTP/2 を任せるのが一般的。

パフォーマンスと代替フレームワーク

Express は「使いやすさ」と「拡張性」を優先した設計で、純粋なパフォーマンスでは環境や用途によって Fastify や uWebSockets などに劣る場合があります。特に大量の同時接続や低レイテンシを追求する場合、以下の点を検討します。

  • Fastify: JSON シリアライズの最適化やプラグインアーキテクチャにより高性能を謳う。
  • Koa: 同じ作者の設計思想を受け継ぎつつ、ミドルウェアがより軽量で async/await を前提にしている。
  • ネイティブ Node.js http/2 モジュール: HTTP/2 を直接活用する場合は追加の検討が必要(Express は直接のHTTP/2サポートを前提に作られていない)。

TypeScript と Express

Express は TypeScript と組み合わせても広く使われています。型定義は @types/express で提供されており、Request, Response, NextFunction などの型を使って安全に開発できます。型安全性を高めるために、ルートハンドラやミドルウェアのインターフェースを明確にし、カスタムプロパティは宣言マージで拡張するのが一般的です。

よくある落とし穴

  • ミドルウェアの順序ミスで認証前にレスポンスが返ってしまう。
  • async ハンドラの例外を next へ渡さずプロセスが落ちる。
  • 本番で Node を単体で公開して TLS/静的配信を任せてしまう(リバースプロキシを使うのが推奨)。
  • パフォーマンスボトルネックをアプリ側の同期処理に置く(fs の同期処理や重い計算は避ける)。

まとめ

Express はシンプルさと拡張性を両立したフレームワークであり、多くのプロジェクトで高速に開発を進めるための良い選択肢です。セキュリティやパフォーマンス要件が高い場合はミドルウェアやインフラ構成で補完するか、用途に応じて Fastify や Koa などの代替を検討してください。Express のエコシステムは成熟しており、実運用に必要なツールやベストプラクティスが豊富に存在します。

参考文献