【Node.js】Express.jsを使う場合と使わない場合でWeb API作成方法を比較してみた
リテールアプリ共創部のるおんです。先日バックエンドの初期開発において Express.js の環境構築を実装する機会がありました。自分自身Express.jsを用いてバックエンド開発を行ったことがなかったのでこのフレームワークを導入するメリットがわかりませんでした。そのため、今回はNode.jsでWeb APIを作成する際に、Express.jsを使用する場合と使用しない場合の違いを比較してみました。Express.jsを使用せずにNode.js単体で実装した場合とコードを比較することで、Express.jsを使用する利点を具体的に理解できると思います。
Express.jsとは
Express.jsは、Node.js用の高速で最小限の機能を持つWebアプリケーションフレームワークです。2010年に最初のバージョンがリリースされて以来、Node.jsエコシステムの中で最も人気のあるフレームワークの一つとして長年にわたり使用されてきました。Webアプリケーションやモバイルアプリケーションのバックエンドを構築する際によく使用されます。Express.jsは、ルーティング、ミドルウェア、テンプレートエンジンなどの機能を提供し、開発者がより簡単にWebアプリケーションを構築できるようサポートしてくれます。
フレームワークというと、RubyだとRuby on RailsやphpだとLaravelなどのフルスタックフレームワークを想起される方もいると思いますが、Express.jsは必要最小限の機能を提供し、開発者が必要に応じて機能を追加していく形式を取るため、学習コストが比較的低いことが特徴の一つです。
セットアップ
Express.jsを使用するために環境構築をします。適当なプロジェクトを作成してExpress.jsのパッケージをインストールしてください。
mkdir express-test-app
cd express-test-app
npm install express
Node.js単体とExpress.jsの比較
それではNode.js単体とExpress.jsを使用した場合のコードを比較してみます。以下の4つの機能について実装し比較します。
- サーバー作成
- ルーティング
- ミドルウェア
- 静的ファイル表示
1. サーバーの作成
まず、最も基本的な機能であるサーバーの作成を比較してみましょう。index.js
ファイルを作成して、以下コードを作成します。
以下は、3000番ポートで文字列を返却するだけの簡単な実装です。
Express.jsを使用した場合:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
})
app.listen(3000, () => {
console.log('Server started on port 3000');
})
Node.js単体の場合:
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/' && req.method === 'GET') {
res.writeHead(200, { "Content-Type": "text/html" });
res.write('Hello World!');
res.end()
} else {
res.writeHead(404, { "Content-Type": "text/html" });
res.write('404 Not Found');
res.end();
}
});
server.listen(3000, () => {
console.log('Server started on port 3000');
});
動作確認
スクリプトを実行してローカルサーバーを起動します。
node index.js
# 出力:Server started on port 3000
curl
コマンドを使用してサーバにリクエストを送ってみます。
curl http://localhost:3000
# 出力:Hello World!
どうでしょうか。たった数文字の文字列を出力するだけでExpressを使用した方がはるかに短く簡潔に書けるのがわかると思います。具体的には、
-
ルーティングの簡素化
Express.jsではapp.get()
メソッドを使用して、特定のパスとHTTPメソッドに対する処理を簡単に定義できます。Node.js単体の場合、if文を使って手動でURLとメソッドをチェックしないといけません。 -
レスポンス処理の簡略化
Express.jsのres.send()
メソッドによって、適切なContent-Typeヘッダーが自動的に設定されますが、Node.js単体では、res.writeHead()
、res.write()
、res.end()
を個別に呼び出してヘッダー、ボディを設定しないといけません。 -
エラーハンドリングの省略
Express.jsでは、定義されていないルートに対するデフォルトの404処理が自動的に行われます。Node.js単体では、これを明示的に実装する必要があります。 -
サーバー起動の簡素化
Express.jsではapp.listen()
メソッドを使用するだけでサーバーを起動できます。Node.js単体ではhttp.createServer()
とserver.listen()
を別々に呼び出す必要があります。
Express.jsを使用した場合、より簡潔なコードでサーバーを作成できることがわかりました。アプリケーションの規模が大きくなるにつれて、これらの利点はより顕著になります。
2. ルーティング
次に、より複雑なルーティングの実装を比較してみましょう。以下は、GET、POST、PUT、DELETEメソッドに対応するエンドポイントを実装した例です。
Express.jsを使用した場合:
const express = require('express');
const app = express();
// GETリクエスト
app.get('/', (req, res) => {
res.send({
msg: 'GET request'
});
});
// POSTリクエスト
app.post('/', (req, res) => {
res.send({
msg: 'POST request'
});
});
// PUTリクエスト
app.put('/:id', (req, res) => {
res.send({
id: req.params.id,
msg: 'PUT request'
});
});
// DELETEリクエスト
app.delete('/:id', (req, res) => {
res.send({
id: req.params.id,
msg: 'DELETE request'
});
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
Node.js単体の場合:
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
const url = req.url
const method = req.method
// レスポンスを設定する関数
const setResponse = (statusCode, payload) => {
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(payload));
res.end();
};
// ルーティング
if (url === '/' && method === 'GET') {
setResponse(200, { msg: 'GET request' });
} else if (url === '/' && method === 'POST') {
setResponse(200, { msg: 'POST request' });
} else if (url.match(/^\/[0-9]+$/) && method === 'PUT') {
const id = url.slice(1);
setResponse(200, { id: id, msg: 'PUT request' });
} else if (url.match(/^\/[0-9]+$/) && method === 'DELETE') {
const id = url.slice(1);
setResponse(200, { id: id, msg: 'DELETE request' });
} else {
setResponse(404, { msg: 'Not Found' });
}
});
server.listen(3000, () => {
console.log('Server started on port 3000');
});
動作確認
先程と同様にスクリプトを実行してローカルサーバーを起動したらcurl
コマンドを使用して各エンドポイントにリクエストを送ってみます。
curl http://localhost:3000
# 出力: {"msg":"GET request"}
curl -X POST http://localhost:3000
# 出力: {"msg":"POST request"}
curl -X PUT http://localhost:3000/123
# 出力: {"id":"123","msg":"PUT request"}
curl -X DELETE http://localhost:3000/456
# 出力: {"id":"456","msg":"DELETE request"}
どうでしょうか。ルーティングの実装においても、Express.jsを使用した方がはるかに簡潔に書けるのがわかると思います。具体的には、
-
HTTPメソッドに対応するメソッド
Express.jsではapp.get()
,app.post()
,app.put()
,app.delete()
などのメソッドを使用して、各HTTPメソッドに対応するルートを簡単に定義できます。Node.js単体では、req.method
を手動でチェックし、条件分岐でレスポンスを決定する必要があります。 -
パラメータの取得
Express.jsでは:idのような形式でURLパラメータを定義し、req.params.id
で簡単に取得できます。Node.js単体では、URLから手動でパラメータを抽出する必要があります。
Express.jsを使用することで、ルーティングの実装がより直感的かつ簡潔になり、開発効率が大幅に向上することがわかります。特に、多数のエンドポイントを持つ大規模なアプリケーションを開発する場合、これらの利点はより顕著になります。
3. ミドルウェア
ミドルウェアは、リクエストとレスポンスの間で実行される関数で、リクエスト処理の流れを制御したり、共通の処理を挿入したりするのに使用されます。Express.jsでは、app.use()
を使用してミドルウェアを登録します。
HTTPリクエストが発生するたびに、登録されたミドルウェアが呼び出されます。
Node.js単体とExpress.jsでのミドルウェアの実装を比較してみましょう。
Express.jsを使用した場合:
const express = require('express');
const app = express();
const loggerMiddleware = function (req, res, next) {
console.log(`ミドルウェア発動!${req.method}:${req.url}`);
next();
};
// ここでミドルウェアを登録
app.use(loggerMiddleware);
// GETリクエスト
app.get('/', (req, res) => {
res.send({
msg: 'GET request'
});
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
Node.js単体の場合:
const http = require('http');
const loggerMiddleware = (req, res, next) => {
console.log(`ミドルウェア発動! ${req.method} ${req.url}`);
next();
};
const server = http.createServer((req, res) => {
loggerMiddleware(req, res, () => {
if (req.url === '/' && req.method === 'GET') {
res.writeHead(200, { "Content-Type": "text/html" });
res.write('Hello World!');
res.end();
} else {
res.writeHead(404, { "Content-Type": "text/html" });
res.write('404 Not Found');
res.end()
}
});
});
server.listen(3000, () => {
console.log('Server started on port 3000');
});
動作確認
curl
コマンドを使用してサーバにリクエストを送ってみます。するとサーバーのログに、ミドルウェアで定義したログが出力されているのがわかると思います。
Server started on port 3000
ミドルウェア発動! GET /
以下の点でExpress.jsで書くと利点があることがわかります。
-
ミドルウェアの適用
Express.jsではapp.use()
メソッドを使用して、ミドルウェアをアプリケーション全体に簡単に適用できます。Node.js単体では、各リクエストハンドラ内でミドルウェアを手動で呼び出す必要があります。 -
next()関数の扱い
Express.jsではnext()
関数が自動的にミドルウェアに渡されます。Node.js単体では、next()
関数を明示的に定義し、呼び出す必要があります。
Node.js単体の場合、ミドルウェアの実装と統合が複雑になりますが、Express.jsを使用するとapp.use()
メソッドを使って簡単にミドルウェアを追加できます。Express.jsのアプローチは、より直感的で管理しやすいコードになっています。
4.静的ファイル
静的ファイルの提供は、Webアプリケーションの重要な機能の一つです。Express.jsは静的ファイルの提供を簡単に行えるミドルウェアを提供していますが、Node.js単体では手動で実装する必要があります。
以下は、public/images/my_x_icon.jpg
という画像ファイルを提供する例です。
Express.jsの場合:
const express = require('express');
const app = express();
const path = require('path');
// 静的ファイルの提供とキャッシュの有効化
app.use(express.static('public', { maxAge: 86400000 }));
// test-imageリクエスト
app.get('/test-image', (req, res) => {
res.sendFile(path.join(__dirname, '../../', 'public', 'images', 'my_x_icon.jpg'));
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
Node.js単体の場合:
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const getServerFile = (filePath, res) => {
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('Image File Not Found');
} else {
const ext = path.extname(filePath);
const contentType = {
'.jpg': 'image/jpeg',
'.png': 'image/png',
'.js': 'text/javascript',
'.css': 'text/css',
'.html': 'text/html',
}[ext] || 'application/octet-stream';
res.writeHead(200, {
'Content-Type': contentType,
'Cache-Control': 'public, max-age=86400' //キャッシュ指定
});
res.write(data);
res.end();
}
});
};
const server = http.createServer((req, res) => {
const url = req.url;
const method = req.method
console.log(method, req.url);
if (url === '/test-image') {
const imagePath = path.join(__dirname, '../../', 'public', 'images', 'my_x_icon.jpg');
getServerFile(imagePath, res);
} else {
res.writeHead(404, { "Content-Type": "text/html" });
res.write('404 Not Found');
res.end();
}
});
server.listen(3000, () => {
console.log('Server started on port 3000');
});
動作確認
スクリプトを実行してローカルサーバーを起動して、http://localhost:3000/test-image
にアクセスすると以下のように静的ファイルが返却されます。
Node.js単体の場合、静的ファイルの提供には多くのコードが必要で、ファイルの読み込みやContent-Typeの設定、エラーハンドリングなどを手動で行う必要があります。これを毎回書くのは嫌ですよね。一方で、Express.jsを使用するとexpress.static()
ミドルウェアを使用して簡単に静的ファイルのディレクトリを指定できます。また、res.sendFile()
メソッドを使用して個別のファイルを送信することも容易です。
Express.jsのアプローチは、コードがより簡潔になり、開発者が低レベルの詳細に悩まされることなく、アプリケーションのロジックに集中できるようになります。
Express.jsのメリット
どうでしたでしょうか?これらの比較を通じて、明らかにExpress.jsを使用した方がNode.jsを単体で使用するよりも楽に短く、直感的にコードを記述できることがわかったと思います。
最後に、Express.jsを使用するメリットを再度まとめておきたいと思います。
1. コードの簡潔さ
Express.jsを使用することで、より少ないコード量で同じ機能を実装できます。特に、ルーティングやミドルウェアの実装において、この利点が顕著に現れます。
2. 可読性の向上
Express.jsの直感的なAPIにより、コードの意図が理解しやすくなります。例えば、app.get()
やapp.post()
などのメソッドは、if文で条件分岐するよりメソッド名から機能が明確に伝わります。
3. 開発効率の改善
ルーティング、ミドルウェア、静的ファイルの提供など、Web開発で頻繁に必要となる機能が簡単に実装できるため、開発速度が大幅に向上します。
4. エラーハンドリングの簡素化
Express.jsには組み込みのエラーハンドリング機能があり、エラーの処理が容易になります。また、404エラーなどのデフォルト処理も自動的にやってくれます。
5. ミドルウェアの豊富さと柔軟性
Express.jsは多くのサードパーティミドルウェアと互換性があり、機能の拡張が容易です。また、カスタムミドルウェアの作成と適用も簡単です。
6. 静的ファイル提供の簡素化
express.static()
ミドルウェアを使用することで、静的ファイルの提供が非常に簡単になります。ファイルタイプの自動検出やキャッシュ制御も容易に行えます。
これらのメリットによりExpress.jsが長い間使用され続けていることがわかりました。
また、学習コストがそれほど高くないのも個人的にはいいと思うポイントでした!
おわりに
今回、Express.jsを使用する場合としない場合のコードを実際に自分で書いてみて比較することで、Express.jsというフレームワークのメリットと存在意義をよく理解することができました。
今回のようにこのフレームワークを使うことで何が嬉しいのかをしっかりと理解して言語化することがとても重要であることを再認識することができてよかったです。
参考になれば幸いです。