「プログラミングはある程度勉強しているけどサーバー系の話がよくわかっていない」
「Web制作を中心にやってきたためリクエスト、レスポンスを練習してきていない」
「Node.jsにいろんなメソッドが登場するけど何をするためのものか分からず使っている」
本日はそんな方に向けてNode.jsの基本であるリクエストとレスポンスを解説していきます。
Node.jsを使用してWebアプリケーションを開発する際、リクエストとレスポンスの処理は重要な要素です。
しかしこれらの概念や実装方法は初心者にとってはしばしば混乱を招くことがあります。
そこでこの記事ではNode.jsでリクエストとレスポンスを確実に制御するための手法に焦点を当てます。
listen
、http
、setHeader
、createServer
、statusCode
などの重要な機能やメソッドを探求しながら、より効率的で安全なWebアプリケーションを構築するためのベストプラクティスを紹介します。
Node.jsでローカルサーバーを用意する方法
まずはNode.jsでローカルサーバーを準備するところからです。
PHPではApacheと言うものがサーバーを用意して起動するところまで自動でやってくれて、開発者はMAMPのようなツールをマウスでクリックして環境を用意することができます。
しかしNode.jsの場合はコードを書くことで同じことをやる必要があります。
一番基本のやり方としてはhttpモジュールというものを使用するものです。
const http = require('http');
const server = http.createServer((req, res) => {
console.log('リクエスト!');
});
httpモジュールはNode.jsに標準で搭載されているモジュールでrequireメソッドでインポートすれば使えるようになります。
その中でcreateServerメソッドというメソッドでローカルサーバーを作成することになり、引数にはリクエスト、レスポンスを使用します。
一般的にリクエストのことをreq、レスポンスのことをresと言うキーワードにすることが多いです。
続いてリクエストを待ち受けるためのlistenメソッドを使用して、第一引数にポート番号を指定います。
ポート番号とはサーバーの入り口、部屋番号のようなものだと思って大丈夫です。
基本的に1つのプロジェクトにつき1つの入り口を使う仕様になっていて、ローカルサーバーだと3000を指定すれば問題ないことが多いですがすでに別のアプリケーションで使用してる場合はエラーが出ます。
その場合は3001,3002…と番号をずらして実行してエラーが出なくなる場所を使うことになります。
const http = require('http');
const server = http.createServer((req, res) => {
console.log('リクエスト!');
});
// ここを追加
server.listen(3000, 'localhost', () => {
console.log('ポート3000!');
});
またlistenメソッドの第二引数にはホスト名を指定します。
ホスト名とは「https://〇〇.com」の部分になり、日常生活で使っているドメインやアドレスと似たものという認識で大丈夫です。
ローカルホストの場合は「localhost」というキーワードを指定します。
さらにlistenメソッドの第三引数には関数型にしてリクエストを受け取った時の処理内容を書きます。
「node ファイル名」をターミナルで実行するとconsole.logで書いた「リクエスト!」が表示されて、続いてブラウザに「http://localhost:3000」を入力してEnterキーを押すとターミナルにconsole.logで書いた「ポート3000!」というメッセージが出るはずです。
コードの実行とサーバーの起動は別々の処理になっているわけです。
ただしブラウザを立ち上げているだけでレスポンスは返していないので、画面には何も表示されずローディング状態でそのまま放置していると自動でサーバーが停止するような状態です。
ちなみにlocalhost:3000というものはドメインとも呼ばれていますが、書籍や教材で「127.0.0.1」という形で紹介されていることもあります。
両方とも同じ意味で「localhost:3000」はドメインで、「127.0.0.1」はIPアドレスになります。
パソコンやサーバーには固有の住所であるIPアドレスが振られていますが、それをそのまま使うのではなくドメインという人間が英語で理解できるキーワードに変換して使用しています。
ただし変換しているだけで同じ意味であるわけで、どちらで起動しても自分のパソコンをホストにしてサーバーを立ち上げることには変わりありません。
そして現時点では自分のパソコンにリクエストを送っている状態でレスポンスは返していませんのでレスポンスを作ります。
const http = require('http');
const server = http.createServer((req, res) => {
console.log('リクエスト!');
// ここを追加
res.setHeader('Content-Type', 'text/plain');
res.write('test');
res.end();
});
server.listen(3000, 'localhost', () => {
console.log('ポート3000!');
});
createServerの第二引数にresと言う引数でレスポンスを指定していました。
resに対してsetHeaderとwriteと言うメソッドを書くことで「画面に何を表示するか」と言うレスポンスを送信したことになります。
一度「Ctrl+C」でサーバーを落として再度サーバーを「node ファイル名」で立ち上げて、ブラウザをリロードしましょう。
res.writeで書いた「test」と言う文字が画面に表示されています。
これでリクエストとレスポンスが完結したことになります。
まずres.setHeaderでどんなデータの種類にするかを決めていて、上記のコードだとテキスト形式にしています。
続いてres.writeで表示したいテキストを記載して、最後にres.end()とすることでレスポンスの終わりを示しています。
しかし一般的にはテキスト形式ではなくHTML形式で画面を表示させるので以下のように変更します。
const http = require('http');
const server = http.createServer((req, res) => {
console.log('リクエスト!');
// ここを変更
res.setHeader('Content-Type', 'text/html');
res.write('<h1>test</h1>');
res.end();
});
server.listen(3000, 'localhost', () => {
console.log('ポート3000!');
});
サーバーを再起動してブラウザをリロードしましょう。
今度は「test」と言う文字がHTMLのh1タグとして表示されましたね。
ブラウザをリロードして検証ツールの「ネットワーク」タブを開いて「ヘッダー」項目を確認するとsetHeaderで指定したHTML形式になっていることからも現在の画面はHTMLで表示できていることがわかります。
もっと言うとres.writeの引数でマークダウンを長々と書くわけにはいきませんので、index.htmlのようなファイル形式のHTMLを読み込む形になります。
「./views/index.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>Document</title>
</head>
<body>
<h1>こんにちは</h1>
</body>
</html>
Node.jsでファイルを読み込むにはfsモジュールが必要なのでrequireメソッドでインポートします。
const http = require('http');
// ここを追加
const fs = require('fs');
const server = http.createServer((req, res) => {
console.log('リクエスト!');
res.setHeader('Content-Type', 'text/html');
res.write('<h1>test</h1>');
res.end();
});
server.listen(3000, 'localhost', () => {
console.log('ポート3000!');
});
fsモジュールではreadFileと言うメソッドでHTMLファイルなどファイル形式のデータを読み込むことが可能になります。
コードを以下のように作り替えます。
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
console.log('リクエスト!');
// ここを変更
fs.readFile('./views/index.html', (err, data) => {
if (err) {
console.log(err);
res.end();
} else {
res.write(data);
res.end();
}
});
});
server.listen(3000, 'localhost', () => {
console.log('ポート3000!');
});
サーバーを再起動してブラウザをリロードします。
index.htmlで書いた内容に画面が切り替わっています。
ここでようやくWebサイトの基本が完成したことになります。
ちなみにres.write()とres.end()は以下のような1行にしても同じ意味になります。
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
console.log('リクエスト!');
fs.readFile('./views/index.html', (err, data) => {
if (err) {
console.log(err);
res.end();
} else {
// ここを変更
res.end(data);
}
});
});
server.listen(3000, 'localhost', () => {
console.log('ポート3000!');
});
Node.jsでルーティングを設定する方法
前章まででHTMLファイルを画面に表示するというWeb開発の基本を実践できました。
応用として複数のHTMLファイルを用意してURLの変更によって画面を切り替えるルーティングについてやっていきます。
先に以下のようにHTMLファイルを用意しておきます。
- views
|- index.html
|- about.html
|- 404.html
- script.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>こんにちは</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>アバウト</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>ページが見つかりませんでした</h1>
</body>
</html>
script.jsについては前章の内容を引き継いで以下のようにしておきます。
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'text/html');
fs.readFile('./views/index.html', (err, data) => {
if (err) {
console.log(err);
res.end();
} else {
res.end(data);
}
});
});
server.listen(3000, 'localhost', () => {
console.log('ポート3000!');
});
現状だとreadFileの第一引数にindex.htmlのみの指定をしていますので、こちらをpathと言う名前で変数にします。
変数pathは「http://localhost:3000/〇〇」の部分の文字列が入るようにして、その文字列の内容によってどのHTMLファイルを読み込むかを条件分岐で作ります。
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'text/html');
// ここを追加
let path;
switch (req.url) {
case '/':
path = './views/index.html';
break;
case '/about':
path = './views/about.html';
break;
default:
path = './views/404.html';
break;
}
// ここを変更
fs.readFile(path, (err, data) => {
if (err) {
console.log(err);
res.end();
} else {
res.end(data);
}
});
});
server.listen(3000, 'localhost', () => {
console.log('ポート3000!');
});
「http://localhost:3000」だとindex.htmlで、「http://localhost:3000/about」だとabout.htmlを読み込み、それ以外だと404.htmlを表示するようにしています。
サーバーを再起動してブラウザをリロードしてみます。
URLを手動で書き換えると、先ほどの条件分岐に沿ったHTMLファイルの表示になることがわかります。
これがルーティングの基本になります。
さらに404.htmlについては間違ったURLを指定されたときに表示するエラー用のページとした場合、画面の表示だけでなくステータスコードも変更しておく必要があります。
ステータスコードは検証ツールの「ネットワーク」タブを開いてヘッダー項目を確認すると表示される「200」のような数字の部分です。
正しい表示になっているときは「200」でエラーの場合は「404」にするのが決まりになっていて、厳密にはエラーの種類によって「403」「500」など種類が分かれます。
今回は404.htmlの表示には「404」にステータスコードを指定するようにします。
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'text/html');
let path;
switch (req.url) {
case '/':
path = './views/index.html';
// ここを追加
res.statusCode = 200;
break;
case '/about':
path = './views/about.html';
// ここを追加
res.statusCode = 200;
break;
default:
path = './views/404.html';
// ここを追加
res.statusCode = 404;
break;
}
fs.readFile(path, (err, data) => {
if (err) {
console.log(err);
res.end();
} else {
res.end(data);
}
});
});
server.listen(3000, 'localhost', () => {
console.log('ポート3000!');
});
ステータスコードの細かい種類については本記事では紹介しませんが気になる方は調べてみると勉強になります。
サーバーを再起動してブラウザをリロードして、404.htmlが表示されるような間違ったURLを指定すると検証ツールの「ネットワーク」タブのヘッダーの記載が「404」に変わっていることが確認できます。
最近のWeb開発では404.htmlのようなエラー画面を出さずに、間違ったURLから推測して別のページに自動で変更する「リダイレクト」と言う手法がよく使われます。
リダイレクトにもステータスコードが「301」として用意されていて、例えば以下のように書き換えます。
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'text/html');
let path;
switch (req.url) {
case '/':
path = './views/index.html';
res.statusCode = 200;
break;
case '/about':
path = './views/about.html';
res.statusCode = 200;
break;
// ここを追加
case '/about1':
res.setHeader('Location', '/about');
res.statusCode = 301;
res.end();
break;
default:
path = './views/404.html';
res.statusCode = 404;
break;
}
fs.readFile(path, (err, data) => {
if (err) {
console.log(err);
res.end();
} else {
res.end(data);
}
});
});
server.listen(3000, 'localhost', () => {
console.log('ポート3000!');
});
「/about」を間違った想定で新しく条件を追加しています。
「/about1」と言うページは自動的に「/about」の条件に切り替えてabout.htmlを表示することになります。
サーバーを再起動してブラウザをリロードして、「http://localhost:3000/about1」と入力してEnterキーを押すと自動的に「http://localhost:3000/about」に切り替わります。
実際にはもう少し便利な作り方があるのですがリダイレクトの基本はこのようなコードで実現することができます。