
Node.JSのEXPRESSを使ってToDo開発
イントロダクション
この度はNode.jsを使ったサーバー開発に初めて挑戦する方へ向けた記事をお読みいただきありがとうございます。
この記事はExpressやHelmet、Corsなどのライブラリを利用して安全で機能豊富なToDoアプリを作成する手順を解説します。
Node.jsのインストールから各種パッケージの導入、サーバーの基本設定とAPI実装、そしてHTMLとフロントエンドとの連携まで丁寧に説明します。
コード内に記載したコメントやconsole.logの出力例も交えながら進めるため理解しやすい内容となっています。どうぞ最後までお付き合いください。
開発環境とパッケージの準備
Node.jsのインストールと基本セットアップ(まだやっていない方向け)
Node.jsはJavaScriptでサーバーサイドの開発を可能にする実行環境です。
公式サイトから最新版をダウンロードしインストールする手順は非常に簡単です。
インストール後ターミナルにて「node -v」と入力するとバージョン番号が表示され正しく導入されていることが確認できます。
今回のサンプルコードはserver.jsとindex.htmlという2つのファイルで構成されています。
server.jsはサーバーの処理全体を記述したファイルでありindex.htmlはフロントエンドの表示用に用意されています。
また、エディタとしてVisual Studio Codeなどを利用することを推奨します。プロジェクト用のフォルダを作成しその中に各ファイルを配置することで管理がしやすくなります。
Node.js自体のインストール作業はシンプルでありながらも後の開発基盤となる重要なステップです。
実際にターミナルでコマンドを実行する際の注意点としてパスの設定や管理者権限が必要な場合がある点に気を付けてください。
これにより環境が整備されるとともに、後続のプログラム実行に問題が発生しにくくなります。
環境構築は最初の大切な一歩であり、確実に実施することでトラブルを未然に防ぐ効果もあります。
ライブラリのインストール手順と注意点
今回のサーバー開発にはExpress、Helmet、Corsというライブラリが必要です。
これらはパッケージ管理ツールnpmを利用してインストールします。
具体的には「npm install express helmet cors」とコマンドを実行します。
各ライブラリはそれぞれサーバー機能、セキュリティ対策、クロスオリジン通信の制御を行う役割を持っています。
Expressはサーバーの基本処理を簡略化するためのフレームワークであり、シンプルな記述でルーティングやミドルウェアの管理が可能です。
HelmetはHTTPヘッダーを適切に設定し攻撃からサーバーを保護するために使用されます。
Corsは異なるオリジン間で安全な通信を可能にする役割を果たします。
これらのライブラリは必ず事前にインストールする必要がありライブラリが存在しない場合はエラーが発生し正常な動作ができません。
コード内では「const express = require(‘express’);」と記述してライブラリの読み込みを行います。
各パッケージが正常に読み込まれるとサーバー起動時に「Server is running on port http://localhost:3000」といったログが表示され動作状況が把握できます。
インストール作業は初心者でも問題なく実行可能な手順であり各ライブラリの役割を理解することで今後の応用範囲が広がります。
事前準備を怠らずにしっかりと環境を整えることがプロジェクト成功の鍵となります。
最終的に目指すファイル構成はこんな感じです。
├── index.html
├── node_modules
├── package-lock.json
├── package.json
└── server.js
Expressとセキュリティ設定の解説
Expressの基本設定とミドルウェア導入
ExpressはNode.js環境でサーバーの基本機能を提供するライブラリです。
server.jsの冒頭では「const app = express();」と記述しアプリケーションのインスタンスを生成しています。
次に「app.use(express.json());」でリクエストボディのJSON解析を行い「app.use(express.static(path.join(__dirname)));」で静的ファイルの提供を行っています。
これによりindex.htmlなどのファイルがサーバー経由でクライアントに返却されます。
さらに独自のロギングミドルウェアを実装しておりリクエスト受信時に「console.log([${new Date().toISOString()}] ${req.method} ${req.url} 開始);」という形で開始時刻とメソッド・URLが出力されます。
レスポンス完了後も「console.log([${new Date().toISOString()}] ${req.method} ${req.url} 完了 (${duration}ms));」と処理時間をログに記録します。
これによりサーバーのパフォーマンスやエラー発生時の原因追及が容易になります。
実際の出力例としては以下のような内容がコンソールに表示されます。
// [2025-02-14T12:00:00.000Z] GET / 開始
// [2025-02-14T12:00:01.000Z] GET / 完了 (1000ms)
各ミドルウェアはリクエスト処理の前後に自動的に実行されるため、コード内に明示的な呼び出しは不要です。
これによりアプリケーション全体で一貫したログ管理が実現されます。Expressの設定はシンプルながらも拡張性が高く、今後の機能追加やカスタマイズも容易に行えます。
基本設定を正しく行うことで開発時のトラブルシュートが円滑に進みます。
HelmetとCORSで安全対策を実装
HelmetはHTTPヘッダーを適切に設定し攻撃からサーバーを保護するためのミドルウェアです。
コード内では「app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: [”‘self’”], scriptSrc: [”‘self’”, “‘unsafe-inline’”, “‘unsafe-eval’”], styleSrc: [”‘self’”, “‘unsafe-inline’”], imgSrc: [”‘self’”, ‘data:’, ‘https:’], “script-src-attr”: [”‘unsafe-inline’”] } } }));」と記述し詳細なセキュリティポリシーを設定しています。
各ディレクティブは外部からの不要なリソース読み込みを制限しスクリプトやスタイルの安全性を担保します。
たとえばdefaultSrcは自己オリジンのみを許可しscriptSrcはインラインスクリプトの実行も可能としつつevalの利用も許容していますがこれらは開発時の便宜を図るための設定です。
Corsは「app.use(cors());」と記述しクロスオリジンリクエストを許可する仕組みを導入します。
これにより、他ドメインからのAPIアクセスが可能となりフロントエンドとバックエンド間の通信が円滑に行えます。
HelmetやCorsの導入はセキュリティ上重要な役割を果たすため正確な設定が必要です。
設定内容はプロジェクトの要件に合わせて調整可能であり運用環境においては更なる強化策を検討することも大切です。
これらのミドルウェアはセキュリティ対策として基本的な防御線を提供し安全なWebアプリケーションの構築に大いに役立ちます。
APIエンドポイントとHTML連携の実装
タスク管理APIの各処理の説明
// server.js
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const path = require('path');
const app = express();
const port = 3000;
// Helmetの設定を調整
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
"script-src-attr": ["'unsafe-inline'"]
},
},
}));
app.use(cors());
app.use(express.json());
app.use(express.static(path.join(__dirname)));
// ロギングミドルウェアを追加
app.use((req, res, next) => {
const start = Date.now();
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url} 開始`);
// レスポンス終了時のログ
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url} 完了 (${duration}ms)`);
});
next();
});
// ルートパスへのアクセスを設定
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
// 仮のデータストア
let tasks = [
{ id: 1, title: 'Learn Express', completed: false },
{ id: 2, title: 'Build a To-Do App', completed: false }
];
// 全タスクを取得
app.get('/api/tasks', (req, res) => {
console.log('タスク一覧を取得します');
res.json(tasks);
console.log(`現在のタスク数: ${tasks.length}`);
});
// 特定のタスクを取得
app.get('/api/tasks/:id', (req, res) => {
const id = parseInt(req.params.id, 10);
const task = tasks.find(t => t.id === id);
if (!task) {
return res.status(404).json({ message: 'Task not found' });
}
res.json(task);
});
// 新しいタスクを作成
app.post('/api/tasks', (req, res) => {
const newTask = {
id: tasks.length + 1,
title: req.body.title,
completed: false
};
tasks.push(newTask);
console.log('新しいタスクが作成されました:', {
id: newTask.id,
title: newTask.title,
timestamp: new Date().toISOString()
});
res.status(201).json(newTask);
});
// タスクを更新
app.put('/api/tasks/:id', (req, res) => {
const id = parseInt(req.params.id, 10);
const taskIndex = tasks.findIndex(t => t.id === id);
if (taskIndex === -1) {
console.log(`タスク更新失敗: ID ${id} が見つかりません`);
return res.status(404).json({ message: 'Task not found' });
}
const oldTask = tasks[taskIndex];
tasks[taskIndex] = { ...oldTask, ...req.body };
console.log('タスクが更新されました:', {
id: id,
変更前: oldTask,
変更後: tasks[taskIndex],
timestamp: new Date().toISOString()
});
res.json(tasks[taskIndex]);
});
// タスクを削除
app.delete('/api/tasks/:id', (req, res) => {
const id = parseInt(req.params.id, 10);
const taskIndex = tasks.findIndex(t => t.id === id);
if (taskIndex === -1) {
console.log(`タスク削除失敗: ID ${id} が見つかりません`);
return res.status(404).json({ message: 'Task not found' });
}
const deletedTask = tasks.splice(taskIndex, 1);
console.log('タスクが削除されました:', {
削除したタスク: deletedTask[0],
残りのタスク数: tasks.length,
timestamp: new Date().toISOString()
});
res.json(deletedTask);
});
// エラーハンドリング
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ message: 'Something went wrong!' });
});
app.listen(port, () => {
console.log(`Server is running on port http://localhost:${port}`);
});
サンプルコードではタスク管理を行うためのAPIエンドポイントが複数定義されています。
まず「app.get(’/api/tasks’, …)」では全タスク一覧を取得しリクエスト受信時に「console.log(‘タスク一覧を取得します’);」と表示します。
続いて「console.log(現在のタスク数: ${tasks.length});」と現在の件数を出力します。
個別タスクを取得する「app.get(’/api/tasks/:id’, …)」ではパラメータからIDを取り出し該当タスクが存在しなければ404エラーを返却します。
新規タスク作成では「app.post(’/api/tasks’, …)」と記述しリクエストボディからタイトルを取得してタスクを追加します。
追加後「console.log(‘新しいタスクが作成されました:’, { id: newTask.id, title: newTask.title, timestamp: new Date().toISOString() });」と生成時刻を含めた情報を出力します。
更新処理は「app.put(’/api/tasks/:id’, …)」で行われ該当タスクがなければエラーメッセージを返します。
更新に成功すると変更前後の内容をログに記録します。削除処理も同様に「app.delete(’/api/tasks/:id’, …)」で実装され削除成功時に削除したタスクと残件数を出力します。
各エンドポイントはRESTfulな設計に基づいておりHTTPメソッドごとに明確な処理が分かれているため保守性が高いです。
ログ出力はデバッグ時に非常に有用であり動作確認に役立ちます。
これによりタスク管理機能の動作が可視化され開発者が状況を即時把握できる仕組みとなっています。
HTMLとフロントエンドとの連携
index.htmlはToDoリストの画面を構成するファイルです。
<!-- index.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>To-Do List App</title>
<style>
body { font-family: Arial, sans-serif; }
.task { margin-bottom: 10px; }
.completed { text-decoration: line-through; }
</style>
</head>
<body>
<h1>To-Doリスト</h1>
<!-- 新規タスク追加フォーム -->
<form id="task-form">
<input type="text" id="task-title" placeholder="新しいタスクを入力" required>
<button type="submit">追加</button>
</form>
<!-- タスク一覧表示エリア -->
<div id="task-list"></div>
<script>
const apiUrl = 'http://localhost:3000/api/tasks';
// タスク一覧を取得して表示する関数
async function fetchTasks() {
const res = await fetch(apiUrl);
const tasks = await res.json();
const taskList = document.getElementById('task-list');
taskList.innerHTML = '';
tasks.forEach(task => {
const taskDiv = document.createElement('div');
taskDiv.className = 'task';
const span = document.createElement('span');
span.className = task.completed ? 'completed' : '';
span.textContent = task.title;
const completeBtn = document.createElement('button');
completeBtn.textContent = task.completed ? '未完了に戻す' : '完了';
completeBtn.addEventListener('click', () => toggleComplete(task.id, task.completed));
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '削除';
deleteBtn.addEventListener('click', () => deleteTask(task.id));
taskDiv.appendChild(span);
taskDiv.appendChild(completeBtn);
taskDiv.appendChild(deleteBtn);
taskList.appendChild(taskDiv);
});
}
// タスクの追加処理
document.getElementById('task-form').addEventListener('submit', async (e) => {
e.preventDefault();
const title = document.getElementById('task-title').value;
await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title })
});
document.getElementById('task-title').value = '';
fetchTasks();
});
// タスクの削除処理
async function deleteTask(id) {
await fetch(`${apiUrl}/${id}`, { method: 'DELETE' });
fetchTasks();
}
// タスクの完了状態を切り替える処理
async function toggleComplete(id, currentStatus) {
await fetch(`${apiUrl}/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ completed: !currentStatus })
});
fetchTasks();
}
// ページ読み込み時にタスク一覧を表示
fetchTasks();
</script>
</body>
</html>
DOCTYPE宣言によりHTML5文書であることを示しhead内で文字コードとタイトル、簡単なスタイルが設定されています。
body内にはh1タグでタイトルが表示され新規タスクを追加するためのformが設けられています。
form内のinputタグはタスク名の入力欄であり必須属性が付与されています。また、タスク一覧を表示するdivタグも用意され全体のレイアウトが整えられています。
script内ではfetch APIを利用しサーバー上のタスク管理APIと通信する処理が実装されています。
たとえば、ページ読み込み時に「fetchTasks();」が呼び出され全タスクが取得されリストが動的に生成されます。
各タスクには完了状態の切替ボタンと削除ボタンが用意されクリック時に対応するAPIが呼び出されます。
更新や削除後は再度fetchTasksが実行され最新のタスクリストが反映されます。
これによりユーザーはリアルタイムでタスクの追加・更新・削除の結果を確認できる仕組みとなっています。
HTMLとバックエンドの連携は非同期通信を駆使しており、ユーザー体験を損なわずにデータ更新が行われる点が魅力です。
まとめ
本記事ではNode.jsのインストールから各種ライブラリの導入、Expressによる基本設定とセキュリティ対策、さらにはタスク管理APIとHTML連携の実装までを丁寧に解説しました。コード内のコメントやconsole.logの出力例を通して処理の流れが把握できるよう説明しています。各ミドルウェアやエンドポイントの役割を理解することで今後のWebアプリケーション開発に自信が持てるようになるでしょう。しっかりと環境を整え安全対策を施しながら開発を進めることが大切です。今後も学びを深め実践的な技術を習得していただければ幸いです。
Githubでも公開してるよ!
先ほどのコードはGithubで公開しているよ。
ここ見てね!