Koa vs Express — Node.js Webフレームワーク徹底比較
1. 結論
Express は豊富なミドルウェアエコシステムと圧倒的な情報量を武器に、あらゆる規模のプロジェクトで安定した選択肢です。Koa は async/await をネイティブに活かしたモダンな設計で、軽量かつ柔軟なフレームワークを求める開発者に適しています。新規プロジェクトで設計の自由度を重視するなら Koa、チーム開発や既存資産の活用を重視するなら Express を推奨します。
2. 比較表
| 項目 | Koa | Express |
|---|---|---|
| npm ダウンロード数(週間) | 約 120 万 | 約 3,500 万 |
| GitHub スター | 約 35k | 約 66k |
| 最新バージョン(2024 年時点) | 2.x | 4.x(5.x beta) |
| バンドルサイズ(unpacked) | 約 60 KB | 約 210 KB |
| 依存パッケージ数 | 24 | 31 |
| TypeScript 対応 | @types/koa で対応 | @types/express で対応 |
| ビルトインルーター | ❌ なし(別途導入) | ✅ express.Router() |
| ビルトインボディパーサー | ❌ なし(別途導入) | ✅ express.json() 等 |
| ビルトイン静的ファイル配信 | ❌ なし | ✅ express.static() |
| 非同期制御 | ネイティブ async/await | コールバック中心(v5 で改善予定) |
| ミドルウェアモデル | カスケード(玉ねぎ型) | 直列パイプライン |
| エラーハンドリング | try/catch + ctx.throw() | エラーハンドリングミドルウェア |
| 学習コスト | やや高い(自分で組み立てる) | 低い(情報・教材が豊富) |
| エコシステム規模 | 中規模 | 非常に大規模 |
| 開発元 | TJ Holowaychuk ら(Express と同じ) | TJ Holowaychuk ら → OpenJS Foundation |
3. それぞれの強み
Koa の強み
- モダンな非同期制御: フレームワーク全体が
async/awaitを前提に設計されており、非同期処理の記述が自然です - カスケード(玉ねぎ型)ミドルウェア: リクエストの「行き」と「帰り」の両方でロジックを挟めるため、ロギングやレスポンスタイム計測が直感的に書けます
- 極めて軽量なコア: ルーターやボディパーサーすら同梱しないため、不要な機能を一切持ち込まずに済みます
- コンテキストオブジェクト (
ctx):req/resを統合したctxにより、API が整理されています - エラーハンドリングが簡潔:
try/catchでそのまま捕捉でき、Express のような特殊な 4 引数ミドルウェアが不要です
Express の強み
- 圧倒的なエコシステム: Passport、Multer、Helmet など、実戦で必要なミドルウェアがほぼすべて揃っています
- 情報量の多さ: Stack Overflow、Qiita、Zenn、書籍、公式ドキュメントなど、日本語を含む学習リソースが桁違いに豊富です
- バッテリー同梱: ルーター、ボディパーサー、静的ファイル配信がビルトインで、最小構成でもすぐに動きます
- 採用実績: Netflix、Uber、IBM など大規模プロダクションでの実績が豊富で、チームへの説明コストが低いです
- Express 5 への進化: 現在ベータ中の v5 では
async/await対応が強化され、Koa との差が縮まりつつあります
4. コード例で比較
4-1. 基本的な JSON API サーバー
Express
// express-app.ts
import express, { Request, Response, NextFunction } from "express";
const app = express();
app.use(express.json());
// ルーティング
app.get("/api/users/:id", (req: Request, res: Response) => {
const { id } = req.params;
res.json({ id, name: `User ${id}`, framework: "Express" });
});
app.post("/api/users", (req: Request, res: Response) => {
const { name } = req.body;
res.status(201).json({ id: "new-id", name, framework: "Express" });
});
// エラーハンドリング(4 引数の特殊シグネチャ)
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
console.error(err.stack);
res.status(500).json({ error: "Internal Server Error" });
});
app.listen(3000, () => {
console.log("Express server running on http://localhost:3000");
});
Koa
// koa-app.ts
import Koa from "koa";
import Router from "@koa/router";
import bodyParser from "koa-bodyparser";
const app = new Koa();
const router = new Router();
// エラーハンドリング(カスケードの最上位に配置)
app.use(async (ctx, next) => {
try {
await next();
} catch (err: any) {
console.error(err.stack);
ctx.status = err.status || 500;
ctx.body = { error: "Internal Server Error" };
}
});
app.use(bodyParser());
// ルーティング
router.get("/api/users/:id", async (ctx) => {
const { id } = ctx.params;
ctx.body = { id, name: `User ${id}`, framework: "Koa" };
});
router.post("/api/users", async (ctx) => {
const { name } = ctx.request.body as { name: string };
ctx.status = 201;
ctx.body = { id: "new-id", name, framework: "Koa" };
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000, () => {
console.log("Koa server running on http://localhost:3000");
});
ポイント: Express はルーター・ボディパーサーが組み込みですぐ書けます。Koa は
@koa/routerとkoa-bodyparserを別途インストールする必要がありますが、コード自体はasync/awaitで統一されて読みやすくなっています。
4-2. カスケードミドルウェア(レスポンスタイム計測)
Koa の「玉ねぎ型」ミドルウェアが最も活きるユースケースです。
Express
// express-timing.ts
import express, { Request, Response, NextFunction } from "express";
const app = express();
// レスポンスタイム計測ミドルウェア
app.use((req: Request, res: Response, next: NextFunction) => {
const start = Date.now();
// レスポンス完了時にヘッダーを付与
res.on("finish", () => {
const ms = Date.now() - start;
// ※ finish イベント時点ではヘッダー送信済みのため、
// 実際にはヘッダーに書き込めない場合がある
console.log(`${req.method} ${req.url} - ${ms}ms`);
});
next();
});
app.get("/", (_req: Request, res: Response) => {
res.json({ message: "Hello Express" });
});
app.listen(3000);
Koa
// koa-timing.ts
import Koa from "koa";
const app = new Koa();
// レスポンスタイム計測ミドルウェア
app.use(async (ctx, next) => {
const start = Date.now();
await next(); // ← 下流のミドルウェアが完了するまで待つ
const ms = Date.now() - start;
ctx.set("X-Response-Time", `${ms}ms`);
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
app.use(async (ctx) => {
ctx.body = { message: "Hello Koa" };
});
app.listen(3000);
ポイント: Koa では
await next()の前後でリクエストの「行き」と「帰り」を自然に表現できます。Express ではres.on("finish")のようなイベントリスナーに頼る必要があり、レスポンスヘッダーへの書き込みタイミングに注意が必要です。
4-3. 非同期エラーハンドリングの違い
Express(v4)
import express, { Request, Response, NextFunction } from "express";
const app = express();
// ❌ Express v4 では async 関数内の例外が自動で捕捉されない
app.get("/danger", async (_req: Request, _res: Response, _next: NextFunction) => {
// この例外は Express に伝わらず、UnhandledPromiseRejection になる
throw new Error("Something went wrong");
});
// ✅ 安全に書くには明示的に next() へ渡す
app.get("/safe", async (req: Request, res: Response, next: NextFunction) => {
try {
throw new Error("Something went wrong");
} catch (err) {
next(err);
}
});
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
res.status(500).json({ error: err.message });
});
app.listen(3000);
Koa
import Koa from "koa";
const app = new Koa();
// グローバルエラーハンドラー
app.use(async (ctx, next) => {
try {
await next();
} catch (err: any) {
ctx.status = err.status || 500;
ctx.body = { error: err.message };
}
});
// ✅ throw するだけで上位の try/catch に自動で伝播する
app.use(async (ctx) => {
ctx.throw(400, "Bad Request");
});
app.listen(3000);
ポイント: Express v4 では
asyncハンドラ内の例外を手動でnext(err)に渡す必要があり、忘れるとプロセスがクラッシュするリスクがあります。Koa ではasync/awaitの仕組みにより、例外が自然にカスケードの上位へ伝播します。
5. どちらを選ぶべきか — ユースケース別推奨
| ユースケース | 推奨 | 理由 |
|---|---|---|
| チーム開発・新メンバーが多い | Express | 学習リソースが豊富で、経験者が多い |
| REST API を素早く構築したい | Express | ビルトイン機能だけで即座に動く |
| マイクロサービスの軽量 API | Koa | コアが軽量で、必要な機能だけ組み込める |
| async/await を徹底したい | Koa | フレームワーク全体が async 前提の設計 |
| 複雑なミドルウェアパイプライン | Koa | カスケードモデルで前後処理が書きやすい |
| 既存の Express 資産がある | Express | 移行コストを避けるのが合理的 |
| Passport / Multer 等を使いたい | Express | 公式対応ミドルウェアがそのまま使える |
| NestJS / Next.js と組み合わせる | Express | デフォルトアダプターとして採用されている |
| GraphQL サーバー(Apollo 等) | どちらでも | 両方にアダプターが用意されている |
| 将来性を重視 | 状況次第 | Express v5 で async 対応が強化予定。Koa は安定しているが開発ペースは緩やか |
6. まとめ
Express と Koa は「同じ作者が設計思想を進化させた」関係にあり、対立するものではなく世代の異なる兄弟です。
- Express は「すぐ動く・情報が多い・エコシステムが巨大」という実務上の安心感が最大の武器です。特に Express v5 が正式リリースされれば、async/await 周りの弱点も大幅に改善される見込みです。
- Koa は「軽量・モダン・柔軟」という設計上の美しさが魅力です。自分でアーキテクチャを組み立てたい上級者や、ミニマルな構成を好むプロジェクトに向いています。
どちらを選んでも Node.js の HTTP サーバーとしての本質は変わりません。チームのスキルセット、プロジェクトの規模、既存資産との整合性を基準に判断するのが、最も実践的な選び方です。