node.jsによるサーバの基本構造
ここでは、JavaScript埋め込みのHTMLファイルとNode.jsサーバを別々に扱い、HTML上からの入力に基づいたPOSTメソッドのリクエストに対応サーバの基本を理解する。これは非常に重要なので、GETメソッドを受け取る単純なサーバの構造と、このサーバの構造は必ず理解しておいて欲しい。
HTMLファイルはブラウザで直接開くか、別のローカルサーバから開くことを想定する。
Node.jsサーバは、APIサーバとして http://localhost:3000 で動作する。
fetch は、JavaScriptからWebサーバへリクエストを送るための命令である。
HTMLファイル
↓ fetch
Node.js API Server
この構成では、ブラウザから見るとHTMLの読み込み元とNode.js APIのアクセス先が異なる場合がある。
そのため、Node.js側でCORSを明示的に許可する。
1. ファイル構成
次の2つのファイルを作成する。
cors-post-test/
server.js
index.html
| ファイル | 役割 |
|---|---|
server.js |
Node.jsのAPIサーバ |
index.html |
ブラウザ側のHTML画面 |
2. server.js
server.js を作成し、次のコードを書く。
const http = require('http');
function setCorsHeaders(res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
}
function readRequestBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => {
body += chunk;
});
req.on('end', () => {
resolve(body);
});
req.on('error', error => {
reject(error);
});
});
}
function sendJson(res, statusCode, obj) {
setCorsHeaders(res);
res.writeHead(statusCode, {
'Content-Type': 'application/json; charset=utf-8'
});
res.end(JSON.stringify(obj, null, 2));
}
const server = http.createServer(async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
if (req.method === 'GET' && req.url === '/') {
sendJson(res, 200, {
status: 'ok',
message: 'Node.js API server is running.'
});
return;
}
if (req.method === 'POST' && req.url === '/submit') {
try {
const body = await readRequestBody(req);
const data = JSON.parse(body);
console.log('Received JSON:', data);
sendJson(res, 200, {
status: 'ok',
message: `Hello, ${data.name}! Your age is ${data.age}.`,
received: data
});
} catch (error) {
sendJson(res, 400, {
status: 'error',
message: 'Invalid JSON',
detail: error.toString()
});
}
return;
}
sendJson(res, 404, {
status: 'error',
message: 'Not Found'
});
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
3. index.html
index.html を作成し、次のコードを書く。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>CORS POST Test</title>
<style>
body {
font-family: sans-serif;
margin: 30px;
background: #f5f5f5;
}
.panel {
background: white;
padding: 20px;
border-radius: 8px;
width: 420px;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
label {
display: block;
margin-top: 12px;
font-weight: bold;
}
input {
width: 100%;
box-sizing: border-box;
margin-top: 5px;
padding: 8px;
font-size: 14px;
}
button {
margin-top: 15px;
padding: 10px 16px;
font-size: 15px;
cursor: pointer;
}
pre {
background: #222;
color: #eee;
padding: 12px;
border-radius: 6px;
overflow-x: auto;
white-space: pre-wrap;
min-height: 80px;
}
#status {
margin-top: 15px;
padding: 10px;
border-radius: 6px;
background: #ddd;
font-weight: bold;
}
.waiting {
background: #dddddd !important;
}
.sending {
background: #90caf9 !important;
}
.success {
background: #a5d6a7 !important;
}
.error {
background: #ef9a9a !important;
}
</style>
</head>
<body>
<h1>CORS POST Test</h1>
<p>
このHTMLファイルから、Node.js APIサーバ
<code>http://localhost:3000/submit</code>
へPOST通信する。
</p>
<div class="panel">
<form id="submitForm">
<label for="name">Name</label>
<input type="text" id="name" value="Taro">
<label for="age">Age</label>
<input type="number" id="age" value="20">
<button type="submit">送信</button>
</form>
<div id="status" class="waiting">状態:待機中</div>
<h2>サーバからの応答</h2>
<pre id="result">まだ送信していません。</pre>
</div>
<script>
const form = document.getElementById('submitForm');
const statusBox = document.getElementById('status');
const result = document.getElementById('result');
function setStatus(text, className) {
statusBox.textContent = '状態:' + text;
statusBox.className = className;
}
form.addEventListener('submit', async event => {
event.preventDefault();
const data = {
name: document.getElementById('name').value,
age: document.getElementById('age').value
};
try {
setStatus('送信中', 'sending');
result.textContent = '';
const response = await fetch('http://localhost:3000/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const responseData = await response.json();
if (response.ok) {
setStatus('完了', 'success');
} else {
setStatus('エラー', 'error');
}
result.textContent = JSON.stringify(responseData, null, 2);
} catch (error) {
setStatus('通信エラー', 'error');
result.textContent = error.toString();
}
});
</script>
</body>
</html>
4. 実行方法
4.1 Node.jsサーバを起動する
コマンドプロンプトで作業フォルダに移動する。
cd cors-post-test
Node.jsサーバを起動する。
node server.js
次のように表示されれば成功である。
Server running at http://localhost:3000/
4.2 サーバの起動確認
ブラウザで次のURLを開く。
http://localhost:3000/
次のようなJSONが表示されれば、Node.jsサーバは起動している。
{
"status": "ok",
"message": "Node.js API server is running."
}
4.3 HTMLファイルを開く
index.html をブラウザで開く。
たとえば、HTMLを直接開いた場合は、URLは次のようになる。
file:///C:/.../cors-post-test/index.html
このHTML画面から、Node.jsサーバの次のURLへPOST通信する。
http://localhost:3000/submit
5. curlでPOSTを確認する
ブラウザを使わずに、コマンドプロンプトからPOST通信を確認することもできる。
Node.jsサーバを起動した状態で、別のコマンドプロンプトを開き、次のコマンドを実行する。
curl -v -H "Content-Type: application/json" -d "{\"name\":\"Taro\",\"age\":30}" http://localhost:3000/submit
成功すると、次のようなJSONが表示される。
{
"status": "ok",
"message": "Hello, Taro! Your age is 30.",
"received": {
"name": "Taro",
"age": 30
}
}
6. CORSの設定
この教材では、HTMLファイルとNode.js APIサーバを別のものとして扱う。
たとえば、HTMLファイルを直接開くと、HTMLの読み込み元は次のようになる。
file:///C:/.../index.html
一方、Node.js APIサーバは次のURLで動作している。
http://localhost:3000/submit
このように、HTMLの読み込み元とAPIの送信先が異なる場合、ブラウザは安全のために通信を制限することがある。
これがCORSである。
6.1 CORS許可ヘッダ
Node.js側では、次の関数でCORSを許可している。
function setCorsHeaders(res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
}
それぞれの意味は次の通りである。
| ヘッダ | 意味 |
|---|---|
Access-Control-Allow-Origin |
どのオリジンからのアクセスを許可するか |
Access-Control-Allow-Methods |
どのHTTPメソッドを許可するか |
Access-Control-Allow-Headers |
どのリクエストヘッダを許可するか |
今回の教材では、学習用に次のようにしている。
res.setHeader('Access-Control-Allow-Origin', '*');
* は、どのオリジンからのアクセスも許可するという意味である。
実際のサービスでは、必要なオリジンだけを許可する方が望ましい。
6.2 OPTIONSリクエスト
ブラウザは、POSTでJSONを送る前に、事前確認として OPTIONS リクエストを送ることがある。
これをプリフライトリクエストという。
今回のNode.jsサーバでは、次の部分で OPTIONS に対応している。
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
204 は、レスポンス本文はないが、処理は成功したことを表すステータスコードである。
7. プログラムの流れ
今回の通信の流れは、次の通りである。
HTML
↓ fetch
Node.js API Server
↓ JSON
HTML
より詳しく書くと、次のようになる。
フォームに入力する
↓
送信ボタンを押す
↓
JavaScriptがfetchを実行する
↓
JSONをPOSTする
↓
Node.jsがPOSTデータを受け取る
↓
Node.jsがJSONを解析する
↓
Node.jsがJSONレスポンスを返す
↓
ブラウザがJSONを受け取る
↓
画面に結果を表示する
8. よくあるエラー
8.1 Node.jsサーバを起動していない
index.html だけを開いても、Node.jsサーバが起動していなければ通信できない。
先に次を実行する。
node server.js
8.2 ポート3000が使われている
すでに別のNode.jsサーバが起動している可能性がある。
その場合は、起動中のサーバを停止する。
コマンドプロンプトで次を押す。
Ctrl + C
8.3 URLが違う
HTML側では、次のURLへ送信している。
fetch('http://localhost:3000/submit', {
Node.js側では、次のURLを処理している。
if (req.method === 'POST' && req.url === '/submit') {
この2つが対応している必要がある。
| HTML側 | Node.js側 |
|---|---|
http://localhost:3000/submit |
/submit |
8.4 CORSエラーが出る
CORSエラーが出る場合は、Node.js側で次のヘッダが設定されているか確認する。
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
また、OPTIONS リクエストに対応しているか確認する。
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
8.5 JSONの形式が間違っている
Node.js側では、次の部分でJSONを解析している。
const data = JSON.parse(body);
送られてきたデータがJSON形式でない場合、エラーになる。
今回のHTMLでは、次のようにJSONへ変換している。
body: JSON.stringify(data)
9. まとめ
この教材では、HTMLファイルからNode.js APIサーバへPOST通信する方法を学習した。
重要な点は次の通りである。
HTML
↓ fetch
Node.js API Server
↓ JSON
HTML
Node.js側では、CORSを許可するために次の処理を入れた。
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
また、プリフライトリクエストに対応するために、OPTIONS メソッドを処理した。
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
この構成を理解しておくと、ローカルHTML、Node.js、GAS Web APIなど、異なる場所で動くWebコンテンツ同士の通信を理解しやすくなる。