はじめに
複数のNode.jsプロダクトを同じ組織で運用していると、あるあるの悩みにぶつかります。Aプロジェクトはミドルウェアの並び順がバラバラ、Bプロジェクトはエラーハンドリングが独自実装、Cプロジェクトはロギングの規約がそもそも無い。Express単体で書き始めると自由度が高い分、プロジェクトごとに「その人なりの型」ができてしまい、新しく入ったメンバーは毎回別物のコードベースを読むことになります。
社内の共通ルール(認証、エラーフォーマット、ロギング)を全プロダクトに配りたくても、コピペで配ると更新が追いつきません。かといって独自の社内フレームワークをフルスクラッチで作るのは、開発コストも保守コストも重すぎます。
Eggは、この悩みに「フレームワークそのものを継承して配布できる仕組み」で応える、Koaベースのエンタープライズ向けNode.jsフレームワークです。単体のアプリを作るだけでなく、Eggを土台にした独自フレームワークを作り、それを複数プロダクトに配布する、という使い方ができるのが最大の特徴です。
Eggとは
Egg(Egg.js)は、Node.jsのKoaを土台にしたWebフレームワークです。GitHubリポジトリ(eggjs/egg)は19,000以上のスターを獲得しており、現在もアクティブにメンテナンスが続いています。公式には「A web framework's framework for Node.js(Node.js向けの、フレームワークのためのフレームワーク)」と説明されており、単にアプリを作るためのフレームワークではなく、その上にさらに独自フレームワークを構築するための土台として設計されています。
主な特徴
- 組み込みプロセス管理 - clusterワーカーの起動・再起動・グレースフルシャットダウンをフレームワーク側が引き受けるため、マルチプロセス構成を自前で実装する必要がありません
- 規約優先のMVC構成 -
controller・service・router・middlewareをディレクトリ規約通りに配置するだけで動作し、設定より規約(Convention over Configuration)を徹底しています - プラグインシステム -
egg-mysqlやegg-redisのように機能単位で独立したプラグインを組み合わせて使え、プラグイン自体もnpmパッケージとして再配布できます - フレームワークカスタマイズ - Eggを継承して独自の上位フレームワークを作れます。社内の共通ルールをパッケージ化し、複数プロダクトへ配布する運用が可能です
- 組み込みセキュリティ - CSRF・XSSなどの対策プラグイン(egg-security)が初期状態で有効になっており、個別に対策コードを書く手間を減らせます
インストール
Eggはスキャフォールディングコマンドでプロジェクトを生成するのが基本の流れです。
mkdir showcase && cd showcase
npm init egg --type=simple
npm install
npm run dev
npm run devでローカルサーバーが起動し、http://localhost:7001でアプリにアクセスできます。既存プロジェクトに追加したい場合は通常のnpmパッケージとしても導入できます。
npm install egg --save
Node.jsは14.20.0以上が必要です。
基本的な使い方
Eggではルーティングをapp/router.jsに、実際の処理をapp/controller/配下に置きます。まずはシンプルなAPIを作ってみます。
// app/router.js
module.exports = (app) => {
const { router, controller } = app;
router.get('/users/:id', controller.user.show);
};
// app/controller/user.js
const { Controller } = require('egg');
class UserController extends Controller {
async show() {
const { ctx } = this;
const { id } = ctx.params;
ctx.body = await ctx.service.user.find(id);
}
}
module.exports = UserController;
ビジネスロジックはコントローラーに直接書かず、app/service/に切り出すのがEggの規約です。同じロジックを複数のコントローラーから呼び出す際に重複を避けられます。
// app/service/user.js
const { Service } = require('egg');
class UserService extends Service {
async find(id) {
// 実際にはDBアクセスなどが入る
return { id, name: `user-${id}` };
}
}
module.exports = UserService;
コントローラーが受け口、サービスが処理の本体、ルーターが両者を結ぶ、という3つの役割分担だけで、規約に沿ったAPIサーバーが組み上がります。
実践的なユースケース
プラグインで機能を足す
Eggの機能はコアとプラグインに分かれており、必要な機能だけをconfig/plugin.jsで有効化して使います。データベース接続やテンプレートエンジンといった機能は、プラグインとして後から足す設計です。
// config/plugin.js
exports.mysql = {
enable: true,
package: 'egg-mysql',
};
// config/config.default.js
exports.mysql = {
client: {
host: 'localhost',
port: '3306',
user: 'root',
password: '',
database: 'test',
},
};
プラグインを有効化すると、コントローラーやサービスからapp.mysqlのようにインスタンスへアクセスできるようになります。使わないプラグインはenable: falseにするだけで完全に切り離せるため、プロジェクトごとに必要な機能だけを積み木のように組み合わせられます。
フレームワークカスタマイズで社内標準を作る
Eggが「フレームワークのフレームワーク」と呼ばれる理由がこの機能です。Eggそのものを継承し、社内で使う共通ロジック(認証、エラーフォーマット、ロギングの規約など)をパッケージ化した独自フレームワークを作れます。
// index.js(独自フレームワークのエントリーポイント)
const path = require('path');
const egg = require('egg');
class MyEnterpriseFramework extends egg.Application {
get [Symbol.for('egg#eggPath')]() {
return path.dirname(__filename);
}
}
module.exports = Object.assign(egg, {
Application: MyEnterpriseFramework,
});
このパッケージをアプリ側のpackage.jsonでegg: { framework: 'my-enterprise-framework' }のように指定すると、Eggの規約に自社ルールを重ねた独自フレームワークとして動作します。複数プロダクトに同じ基盤を配りたい場合、コピペではなくバージョン管理されたnpmパッケージとして配布・更新できるのが大きな利点です。
スケジュールタスクで定期処理を組む
日次集計やキャッシュのクリアといった定期処理は、app/schedule/にファイルを置くだけで登録できます。cronの文字列や実行間隔を指定でき、複数プロセスで動かしても二重実行されないよう調整する仕組みも組み込まれています。
// app/schedule/clean_cache.js
const { Subscription } = require('egg');
class CleanCache extends Subscription {
static get schedule() {
return {
cron: '0 0 4 * * *', // 毎日4時
type: 'worker', // 1ワーカーだけで実行
};
}
async subscribe() {
const { ctx } = this;
await ctx.service.cache.clean();
ctx.logger.info('cache cleaned');
}
}
module.exports = CleanCache;
type: 'worker'にすると複数のクラスターワーカーが立ち上がっていても実行は1回に絞られ、type: 'all'にすると全ワーカーで実行されます。cronライブラリを別途導入せずに、この使い分けだけで済むのはEggの組み込みプロセス管理が活きている部分です。
まとめ
Eggは、Koaをベースにした規約優先のMVCフレームワークでありながら、フレームワークカスタマイズによって「自社の型」そのものをパッケージとして配布できる点が最大の特徴です。プラグインシステムで機能を積み木のように組み合わせられ、組み込みプロセス管理やセキュリティ対策のおかげで、個々のプロジェクトが車輪の再発明をせずに済みます。
単発のAPIサーバーを立てるだけならExpressやKoaでも十分ですが、複数プロダクトにまたがる共通基盤を作りたい、あるいは組織としての開発規約を強制したい場面では、Eggを検討する価値があります。
