Node.js - サーバーサイドのJavaScript
Node.jsは、V8 Javascriptエンジン上に構築されたJavaScriptの実行環境です。サーバーサイドのJavaScript環境であり、非同期、イベント駆動といった特徴があります。
Node.jsとは
Node.js は、V8 Javascriptエンジン上に構築された JavaScript の実行環境です。サーバーサイドの JavaScript 環境であり、非同期、イベント駆動といった特徴があります。
シングルスレッド
Node.js はシングルスレッドで動作します。つまりは、一度に 1 つの処理しかできないということです。しかし、それでは困るので(商品一覧ページを閲覧するために、他の誰かが商品の購入が完了するのを待ちたいですか?)ノンブロッキング I/O がそれを解決します。 ノンブロッキング I/O は、データの送受信(I/O)の完了を待たず(ブロックせず)に、次の処理を受け付け、並列に実行します。前の処理が終わったら、その結果はコールバックで受け取ります。
イベント駆動
イベント駆動とは、イベントや状態の変化によって決定されるアプリケーションのフロー制御です。例えば、フロントエンドで使用される JavaScript はユーザーの「クリック」や「マウスオーバー」といったイベントによって動作します。イベントを監視して、イベントを検知したらコールバックを呼び出します。
Node.js のイベントループは無限に繰り返し実行されています。サーバーによってトリガされるイベント(http リクエストなど)を監視し、イベントを検知した場合にはイベントループのキューへ入れられます。キューに入ったタスク(コールバック)が順番に実行されます。
Node.jsを使用する利点
Node.js をサーバーサイドの言語として採用する利点として、以下の点が上げられます。
- チャット機能や連続的なストリーミングを実装する際に有利。WebSocket 通信を簡単に記述できる Socket.io というライブラリがあります。
- 大量のリクエストを処理することができる
- ライブラリ/エコシステムが(npm)が充実している
- フロントエンド/サーバーサイドで同じ言語を使用できるため、学習コストが低い
Node.jsのインストール
Node.js をインストールするにはいくつか方法がありますが、すべて下記 URL から確認できます。
もっとも簡単な方法はダウンロードページのインストーラーをクリックするだけです。
Node.js のバージョンには LTS(推奨版)と最新版があります。 LTS は Long Term Support ということで、長期のサポートを約束します。 最新版はその名のとおり一番新しいバージョンを利用できますが、バグが入り込んでいる可能性があります。 無難に LTS 版を選ぶべきでしょう。
Node.js のインストールが完了したら、以下のコマンドで正しくインストールされたか確認してみましょう。
node -v
v12.14.1
Node.js をインストールすると、同時に npm も入手できます。ついでに確認してみましょう。
npm -v
6.14.4
Node.jsを実際に使用する
それでは実際に Node.js を使用してみます。手始めに、簡単なサーバーを立てて Hello World と表示させてみましょう。
Node.jsでHello World
// httpモジュールをロード
const http = require("http");
const port = 3000;
http
.createServer((req, res) => {
// レスポンスヘッダー
res.writeHead(200, {
"Content-Type": "text/html",
});
// レスポンスボディ
res.end("<h1>Hello World</h1>");
})
.listen(port); // ポート3000で待ち受ける
HTTP を使って Web で通信するには、http
モジュールを利用します。http
モジュールは、Node.js もコアライブラリの 1 つであるため、Node.js をインストールした際に同時にインストールされています。
Node.js でモジュールをインストールする際には、CommonJS 仕様に従います。モジュールをインポートするには require()
でパスを指定します。require()
のパスが ./
、../
、/
のいずれでも始まらない場合には、コアライブラリ、node_modules
の順に捜索します。
次の行は、このアプリケーションで仕様するポート番号 3000 を代入しています。 開発の Web サーバーでは一般にポート番号 3000 が使われます。
http
モジュールの createServer()
関数は、http.Server
のインスタスを生成します。サーバーのインスタンスを作成したら、アプリケーションは HTTP リクエストを受信し HTTP レスポンスを送信する準備が完了します。
createServer()
はコールバックを引数に受け取ります。
コールバックはリクエストとレスポンスを引数として受け取り、慣例的に req
,res
と表されます。
この res
パラメータを使用して、リクエストをしたユーザーにレスポンスを送り返します。
writeHead()
メソッドでは、リクエストのヘッダを記述します。ここではリクエストの成功を表す「200」ステータスコードとコンテンツを HTML 形式で送るということを表す Content-Type
を記述します。
end()
メソッドでレスポンスのボディを送るとともにレスポンスが終了したことをクライアントに伝えます。ボディを送るだけなら write()
メソッドを使用できますが、その場合でも必ず end()
メソッドを読んでレスポンスの終了を伝える必要があります。
最後に、作成されたサーバーのインスタンスの listen()
メソッドで使用するポート番号を伝えます。
このコードを実行するには、ファイルに保存して(ここでは main.js
という名前にしました)ターミナル上で node <ファイル名>
で実行します。ファイル名の .js
は省略しても構いません。
node main
無事実行できたら、ブラウザでhttp://localhost:3000/をアクセスしてみましょう。Hello World と表示されているはずです。
リクエストを表示する
req
パラメータでは、クライアントからのリクエストを受け取ることができます。
リクエストのログを出力してみましょう。
const http = require("http");
const port = 3000;
http
.createServer((req, res) => {
res.writeHead(200, {
"Content-Type": "text/html",
});
console.log(req.method);
console.log(req.url);
console.log(req.headers);
res.end("<h1>Hello World</h1>");
})
.listen(port);
req.method
はリクエストメソッド、req.url
はリクエストした URL、req.headers
はリクエストヘッダーです。
コードを変更したのなら、サーバーを再起動する必要があります。一度 Cnrl + cで
終了したあとに、再度 node <ファイル名>
で起動してください。
このめんどくさいタスクはnodemonというパッケージを利用すれば削減されます。一般的には必ずといっていいほど使用されるべきですが、今回は小さなアプリケーションのためスキップします。
http://localhost:3000/に訪れたら、ターミナル上で次のようなログが出力されます。
GET
/
{
host: 'localhost:3000',
connection: 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 ... 以下略
}
見事に出力されましたね。試しにhttp://localhost:3000/usersというパスを訪れてみると、出力結果は変わるはずです。
GET
/users
{
host: 'localhost:3000',
connection: 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 ... 以下略
}
ルーティングを追加する
今度は、アプリケーションにルーティングを加えてみます。
req.url
を使用すれば、ユーザーがどの URL に対してアクセスしてきたのかわかるので、それを使用して条件分岐をしましょう。
どのルートにもあてはまらなかった場合には、コンテンツが見つからなかったことを表すエラーコド「404」を返します。
const http = require("http");
const port = 3000;
http
.createServer((req, res) => {
if (req.url === "/") {
res.writeHead(200, {
"Content-Type": "text/html",
});
res.end("<h1>Hello World</h1>");
} else if (req.url === "/users") {
res.writeHead(200, {
"Content-Type": "text/html",
});
res.end(`
<h1>Users</h1>
<ul>
<li>Jone</li>
<li>Aaron</li>
<li>Abel</li>
</ul>
`);
} else {
res.writeHead(400, {
"Content-Type": "text/html",
});
res.end("<h1>Not Found!</h1>");
}
})
.listen(port);
サーバーを再起動して、/users
や存在しないルートを訪問してみてください。
開発者ツールのネットワークログから、404 が返されていることが確認できます。
外部からHTMLファイルを読み込む
ルートも増えてきたので、レスポンスボディのコンテンツは別のフォルダに保存して読み取りましょう。viwes
フォルダを作成して、そこに index.html
と user.html
を作成します。
構成は次のようになります。
- main.js
- views - index.html
- users.html
例として、index.html
を次のように作成しました。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Home Page</title>
</head>
<body>
<h1>Welcome!</h1>
</body>
</html>
Node.js でファイルを読み込むには fs
モジュールが必要です。このモジュールも http
モジュールと同じく Node.js のコアライブラリです。main.js
ファイルの先頭にこのモジュールを使用できるように`const fs = require('fs')を追加します。
ファイルの読み込みは readFile()
関数を使用します。readFile()
関数は第 1 引数にファイル名、第 2 引数にコールバックを受け取ります。
fs.readFile('views/index.html`, (err, data) => {})
コールバックの第 1 引数にはエラーを受け取ります。何らかの理由でファイルが読み込まれなかったときには、このパラメータに値が渡されエラーが有ったことを通知します。
readFile()
関数に限らず、Node.js では非同期処理の例外の取り扱いとして、処理が失敗した場合は、コールバック関数の 1 番目の引数にエラーオブジェクトを渡して呼び出すエラーファーストコールバックが広く使われていました。
ECMAScript 2015(ES2015)で Promise
が仕様に入るまで、非同期処理中に発生した例外を扱う仕様がなかったためです。
現在では、Promise
を使うことが推奨されています。
最終的なコードは次のようになります。
const http = require("http");
const port = 3000;
const fs = require("fs");
http
.createServer((req, res) => {
if (req.url === "/") {
fs.readFile("views/index.html", (error, data) => {
console.log(error);
if (!error) {
res.writeHead(200, {
"Content-Type": "text/html",
});
res.end(data);
} else {
res.writeHead(404, {
"Content-Type": "text/html",
});
res.end("<h1>Not Found!</h1>");
}
});
} else if (req.url === "/users") {
fs.readFile("./views/users.html", (error, data) => {
if (!error) {
res.writeHead(200, {
"Content-Type": "text/html",
});
res.end(data);
} else {
res.writeHead(404, {
"Content-Type": "text/html",
});
res.end("<h1>Not Found!</h1>");
}
});
} else {
res.writeHead(404, {
"Content-Type": "text/html",
});
res.end("<h1>Not Found!</h1>");
}
})
.listen(port);
fs.readFile()
のコールバックのエラーが存在しないときには通常のレスポンスを送り、エラーがあったときには 404 を返します。
express.jsを使用する
一通り簡単なアプリケーションを作成しましたが、このまま進めていったらコードは見るも耐えないものになるのは間違いないです。
そこで、次からは Node.js のフレームワークとして有名な Express.js を使用してアプリケーションを構築します。
また来週へ。