Express.jsをマスター:深い探求
James Reed
Infrastructure Engineer · Leapcell

Expressは、Node.jsで非常に一般的に使用されているウェブサーバーアプリケーションフレームワークです。基本的に、フレームワークとは、特定のルールに従うコード構造であり、2つの重要な特性があります。
- APIをカプセル化し、開発者がビジネスコードの記述により集中できるようにします。
- 確立されたプロセスと標準仕様があります。
Expressフレームワークの主な機能は次のとおりです。
- さまざまなHTTPリクエストに応答するためにミドルウェアを構成できます。
- さまざまなタイプのHTTPリクエストアクションを実行するためのルートテーブルを定義します。
- パラメータをテンプレートに渡して、HTMLページの動的なレンダリングを実現できます。
この記事では、Expressがミドルウェアの登録、nextメカニズム、および単純なLikeExpressクラスを実装することによるルート処理をどのように実装するかを分析します。
Expressの分析
まず、2つのExpressコード例を通じて、それが提供する機能を調べてみましょう。
Expressの公式ウェブサイトのHello Worldの例
const express = require('express'); const app = express(); const port = 3000; app.get('/', (req, res) => { res.send('Hello World!'); }); app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`); });
エントリファイルapp.jsの分析
以下は、express-generator
スキャフォールディングによって生成されたExpressプロジェクトのエントリファイルapp.js
のコードです。
// 一致しないルートによって引き起こされるエラーを処理します const createError = require('http-errors'); const express = require('express'); const path = require('path'); const indexRouter = require('./routes/index'); const usersRouter = require('./routes/users'); // `app`はExpressインスタンスです const app = express(); // View engineの設定 app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); // postリクエストでJSON形式のデータを解析し、`req`オブジェクトに`body`フィールドを追加します app.use(express.json()); // postリクエストでurlencoded形式のデータを解析し、`req`オブジェクトに`body`フィールドを追加します app.use(express.urlencoded({ extended: false })); // 静的ファイル処理 app.use(express.static(path.join(__dirname, 'public'))); // トップレベルのルートを登録します app.use('/', indexRouter); app.use('/users', usersRouter); // 404エラーをキャッチし、エラーハンドラに転送します app.use((req, res, next) => { next(createError(404)); }); // エラー処理 app.use((err, req, res, next) => { // 開発環境でエラーメッセージを表示するためにローカル変数を設定します res.locals.message = err.message; // 環境変数に応じて完全なエラーを表示するかどうかを決定します。開発環境で表示し、本番環境で非表示にします。 res.locals.error = req.app.get('env') === 'development'? err : {}; // エラーページをレンダリングします res.status(err.status || 500); res.render('error'); }); module.exports = app;
上記の2つのコードセグメントから、Expressインスタンスapp
には主に3つのコアメソッドがあることがわかります。
app.use([path,] callback [, callback...])
: ミドルウェアを登録するために使用されます。リクエストパスが設定されたルールに一致すると、対応するミドルウェア関数が実行されます。path
: ミドルウェア関数を呼び出すパスを指定します。callback
: コールバック関数はさまざまな形式を取ることができます。単一のミドルウェア関数、コンマで区切られた一連のミドルウェア関数、ミドルウェア関数の配列、または上記のすべての組み合わせにすることができます。
app.get()
とapp.post()
: これらのメソッドはuse()
に似ており、ミドルウェアを登録するためにも使用されます。ただし、HTTPリクエストメソッドにバインドされています。対応するHTTPリクエストメソッドが使用された場合にのみ、関連するミドルウェアの登録がトリガーされます。app.listen()
: httpServerを作成し、server.listen()
に必要なパラメータを渡す役割を果たします。
コードの実装
Expressコードの機能の分析に基づいて、Expressの実装は次の3つのポイントに焦点を当てていることがわかります。
- ミドルウェア関数の登録プロセス。
- ミドルウェア関数のコアnextメカニズム。
- ルート処理、パスのマッチングに重点を置いています。
これらのポイントに基づいて、以下に単純なLikeExpressクラスを実装します。
1. クラスの基本構造
まず、このクラスが実装する必要のある主なメソッドを明確にします。
use()
: 一般的なミドルウェア登録を実装します。get()
とpost()
: HTTPリクエストに関連するミドルウェア登録を実装します。listen()
: 基本的に、httpServerのlisten()
関数です。このクラスのlisten()
関数では、httpServerが作成され、パラメータが渡され、リクエストがリッスンされ、コールバック関数(req, res) => {}
が実行されます。
ネイティブNode httpServerの使用法を確認してください。
const http = require("http"); const server = http.createServer((req, res) => { res.end("hello"); }); server.listen(3003, "127.0.0.1", () => { console.log("node service started successfully"); });
したがって、LikeExpressクラスの基本構造は次のとおりです。
const http = require('http'); class LikeExpress { constructor() {} use() {} get() {} post() {} // httpServerコールバック関数 callback() { return (req, res) => { res.json = function (data) { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify(data)); }; }; } listen(...args) { const server = http.createServer(this.callback()); server.listen(...args); } } module.exports = () => { return new LikeExpress(); };
2. ミドルウェアの登録
app.use([path,] callback [, callback...])
から、ミドルウェアは関数の配列または単一の関数であることがわかります。実装を簡単にするために、ミドルウェアを関数の配列として一様に処理します。LikeExpressクラスでは、3つのメソッドuse()
、get()
、およびpost()
はすべてミドルウェアの登録を実装できます。トリガーされるミドルウェアは、リクエストメソッドが異なるために異なります。したがって、次のように考えます。
- 一般的なミドルウェア登録関数を抽象化します。
- これらの3つのメソッドのミドルウェア関数の配列を作成して、異なるリクエストに対応するミドルウェアを格納します。
use()
はすべてのリクエストに対する一般的なミドルウェア登録メソッドであるため、use()
ミドルウェアを格納する配列は、get()
およびpost()
の配列の結合です。
ミドルウェアキュー配列
ミドルウェア配列は、クラス内のメソッドが簡単にアクセスできるように、パブリック領域に配置する必要があります。したがって、ミドルウェア配列をconstructor()
コンストラクタ関数に配置します。
constructor() { // 格納されたミドルウェアのリスト this.routes = { all: [], // 一般的なミドルウェア get: [], // getリクエストのミドルウェア post: [], // postリクエストのミドルウェア }; }
ミドルウェア登録関数
ミドルウェアの登録とは、ミドルウェアを対応するミドルウェア配列に格納することを意味します。ミドルウェア登録関数は、受信したパラメータを解析する必要があります。最初のパラメータはルートまたはミドルウェアである可能性があるため、最初にルートであるかどうかを判断する必要があります。ルートの場合は、そのまま出力します。それ以外の場合は、デフォルトはルートルートであり、残りのミドルウェアパラメータを配列に変換します。
register(path) { const info = {}; // 最初のパラメータがルートの場合 if (typeof path === "string") { info.path = path; // 2番目のパラメータから始まる配列に変換し、ミドルウェア配列に格納します info.stack = Array.prototype.slice.call(arguments, 1); } else { // 最初のパラメータがルートでない場合、デフォルトはルートルートであり、すべてのルートが実行されます info.path = '/'; info.stack = Array.prototype.slice.call(arguments, 0); } return info; }
use()
、get()
、およびpost()
の実装
一般的なミドルウェア登録関数register()
を使用すると、use()
、get()
、およびpost()
を簡単に実装できます。ミドルウェアを対応する配列に格納するだけです。
use() { const info = this.register.apply(this, arguments); this.routes.all.push(info); } get() { const info = this.register.apply(this, arguments); this.routes.get.push(info); } post() { const info = this.register.apply(this, arguments); this.routes.post.push(info); }
3. ルートマッチング処理
登録関数の最初のパラメータがルートの場合、リクエストパスがルートに一致するか、またはそのサブルートである場合にのみ、対応するミドルウェア関数がトリガーされます。したがって、後続のcallback()
関数が実行するために、リクエストメソッドとリクエストパスに従って一致するルートのミドルウェア配列を抽出するルートマッチング関数が必要です。
match(method, url) { let stack = []; // ブラウザの組み込みアイコンリクエストを無視します if (url === "/favicon") { return stack; } // ルートを取得します let curRoutes = []; curRoutes = curRoutes.concat(this.routes.all); curRoutes = curRoutes.concat(this.routes[method]); curRoutes.forEach((route) => { if (url.indexOf(route.path) === 0) { stack = stack.concat(route.stack); } }); return stack; }
次に、httpServerのコールバック関数callback()
で、実行する必要があるミドルウェアを抽出します。
callback() { return (req, res) => { res.json = function (data) { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify(data)); }; const url = req.url; const method = req.method.toLowerCase(); const resultList = this.match(method, url); this.handle(req, res, resultList); }; }
4. nextメカニズムの実装
Expressミドルウェア関数のパラメータはreq
、res
、およびnext
であり、next
は関数です。それを呼び出すことによってのみ、ミドルウェア関数を順番に実行できます。これはES6 Generatorのnext()
に似ています。私たちの実装では、次の要件を満たすnext()
関数を作成する必要があります。
- ミドルウェアキュー配列から一度に1つのミドルウェアを順番に抽出します。
next()
関数を抽出されたミドルウェアに渡します。ミドルウェア配列はパブリックであるため、next()
が実行されるたびに、配列内の最初のミドルウェア関数が取り出されて実行され、ミドルウェアの順次実行の効果が実現されます。
// コアnextメカニズム handle(req, res, stack) { const next = () => { const middleware = stack.shift(); if (middleware) { middleware(req, res, next); } }; next(); }
Expressコード
const http = require('http'); const slice = Array.prototype.slice; class LikeExpress { constructor() { // 格納されたミドルウェアのリスト this.routes = { all: [], get: [], post: [], }; } register(path) { const info = {}; // 最初のパラメータがルートの場合 if (typeof path === "string") { info.path = path; // 2番目のパラメータから始まる配列に変換し、スタックに格納します info.stack = slice.call(arguments, 1); } else { // 最初のパラメータがルートでない場合、デフォルトはルートルートであり、すべてのルートが実行されます info.path = '/'; info.stack = slice.call(arguments, 0); } return info; } use() { const info = this.register.apply(this, arguments); this.routes.all.push(info); } get() { const info = this.register.apply(this, arguments); this.routes.get.push(info); } post() { const info = this.register.apply(this, arguments); this.routes.post.push(info); } match(method, url) { let stack = []; // ブラウザの組み込みアイコンリクエスト if (url === "/favicon") { return stack; } // ルートを取得します let curRoutes = []; curRoutes = curRoutes.concat(this.routes.all); curRoutes = curRoutes.concat(this.routes[method]); curRoutes.forEach((route) => { if (url.indexOf(route.path) === 0) { stack = stack.concat(route.stack); } }); return stack; } // コアnextメカニズム handle(req, res, stack) { const next = () => { const middleware = stack.shift(); if (middleware) { middleware(req, res, next); } }; next(); } callback() { return (req, res) => { res.json = function (data) { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify(data)); }; const url = req.url; const method = req.method.toLowerCase(); const resultList = this.match(method, url); this.handle(req, res, resultList); }; } listen(...args) { const server = http.createServer(this.callback()); server.listen(...args); } } module.exports = () => { return new LikeExpress(); };
Leapcell: Webホスティング、非同期タスク、およびRedisの次世代サーバーレスプラットフォーム
最後に、Expressのデプロイに非常に適したプラットフォームを紹介します:Leapcell。
Leapcellは、次の特性を備えたサーバーレスプラットフォームです。
1. 多言語サポート
- JavaScript、Python、Go、または Rustで開発します。
2. 無制限のプロジェクトを無料でデプロイ
- 使用量のみを支払います — リクエストも料金もありません。
3. 比類のないコスト効率
- アイドル料金なしで、従量課金制。
- 例:25ドルで、平均応答時間60ミリ秒で694万リクエストをサポートします。
4. 合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI/CDパイプラインとGitOpsの統合。
- 実用的な洞察のためのリアルタイムのメトリックとログ。
5. 簡単なスケーラビリティとハイパフォーマンス
- 高い同時実行性を簡単に処理するための自動スケーリング。
- 運用上のオーバーヘッドゼロ — 構築に集中するだけです。
Leapcell Twitter: https://x.com/LeapcellHQ