完全なTODOアプリケーションの作成: FastifyとHTMLを使用したフロントエンドとバックエンドの構築
概要
Fastify v4.0.0
nodejs v19.7.0
前半ではFastifyを使用してAPIを構築し、後半ではHTMLとJavaScriptを使用してフロントエンドを作成することで、完全なTODOアプリケーションを作成します。
また、今回作成するコードは全て、GitHubに掲載しています。
APIのエンドポイント:
GET /todos すべての TODO を取得する
POST /todos 新しい TODO を作成する
PUT /todos/:id 既存の TODO を更新する
DELETE /todos/:id 既存の TODO を削除する
フロントエンドの機能:
TODOのリストを表示する
新しいTODOを作成する
既存のTODOを更新する
既存のTODOを削除する
TODOアプリのバックエンドを作成する方法
必要モジュールのインストール
npm install fastify
npm install @fastify/static
npm install @fastify/cors
server.js
// モジュールをインポートします
const fastify = require("fastify")({ logger: true });
const path = require("path");
// CORS設定を有効化します
fastify.register(require("@fastify/cors"), {
origin: true, // すべてのオリジンからのアクセスを許可します
});
// HTMLファイルをホストするディレクトリを指定します。
fastify.register(require("@fastify/static"), {
root: path.join(__dirname, "views"),
});
// ルートにアクセスしたら、view/index.htmlを表示します
fastify.get("/", (req, reply) => {
reply.sendFile("index.html");
});
// 一覧用の Todo を作成します
let todos = [
{ id: 1, text: "野球をする", completed: false },
{ id: 2, text: "ゲームをする", completed: false },
{ id: 3, text: "仕事をする", completed: false },
];
// '/todos'パスで Todo の一覧を取得します
fastify.get("/todos", (req, reply) => {
reply.send({ todos });
});
// "/todos"パスに POST リクエストを送信して Todo を新しく作成します
fastify.post("/todos", (req, reply) => {
const newTodo = req.body;
newTodo.id = todos.length + 1;
todos.push(newTodo);
reply.send({ todo: newTodo });
});
// "/todos/:id"パスに PUTリクエストを送信して Todo を更新します
fastify.put("/todos/:id", (req, reply) => {
const id = req.params.id;
let todo = todos.find((t) => t.id == id);
if (todo) {
Object.assign(todo, req.body);
reply.send({ todo });
} else {
reply.code(404).send({ message: "Todo が見つかりません" });
}
});
// "/todos/:id"パスに DELETE リクエストを送信して Todo を削除します
fastify.delete("/todos/:id", (req, reply) => {
const id = req.params.id;
const index = todos.findIndex((t) => t.id == id);
if (index !== -1) {
todos.splice(index, 1);
reply.send({ message: "Todo が削除されました" });
} else {
reply.code(404).send({ message: "Todo が見つかりません" });
}
});
// ポート 3000 番でサーバーをリッスンします
fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err;
fastify.log.info(`サーバーが ${address} でリッスン中です`);
});
解説
// モジュールをインポートします
const fastify = require('fastify')({ logger: true });
const path = require('path');
上記のコードではFastifyというモジュールとpathというモジュールをインポートしています。Fastifyインスタンスは、HTTPリクエストを処理するためのメソッドを含みます。
// CORS設定を有効化します
fastify.register(require("@fastify/cors"), {
origin: true, // すべてのオリジンからのアクセスを許可します
});
ここでは、CORS(Cross-Origin Resource Sharing)を有効にしています。CORSは、異なるオリジンからのリクエストを許可するための仕組みです。ここでは、すべてのオリジンからのリクエストを許可しています。
// HTMLファイルをホストするディレクトリを指定します。
fastify.register(require("@fastify/static"), {
root: path.join(__dirname, "public"),
});
ここでは、静的なファイル(HTML、CSS、JavaScript、画像ファイルなど)を配信するためのミドルウェアを設定しています。このミドルウェアは、指定されたディレクトリ内のファイルをHTTP経由で直接提供します。
// ルートにアクセスしたら、view/index.htmlを表示します
fastify.get("/", (req, reply) => {
reply.sendFile("index.html");
});
ここでは、ルートにアクセスしたらindex.html
を表示するように設定しています。index.html
は、フロントエンド部分のHTMLファイルです。
// ポート 3000 番でサーバーをリッスンします
fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err;
fastify.log.info(`サーバーが ${address} でリッスン中です`);
});
最後に、サーバーはポート3000でリッスンします。これにより、サーバーはポート3000で受信した全てのHTTPリクエストを処理します。
TODOアプリのフロントエンドを作成する方法
次に、フロントエンド部分を作成します。以下のHTMLとJavaScriptをindex.html
という名前のファイルに保存します。
index.html
<!DOCTYPE html>
<html>
<head>
<title>TODOアプリ</title>
<style>
/* ここにCSSを書く */
</style>
</head>
<body>
<h1>TODOアプリ</h1>
<input id="todo-input" type="text" placeholder="新しいTODOを入力" />
<button id="add-todo">追加</button>
<ul id="todo-list"></ul>
<template id="todo-item-template">
<li>
<span class="todo-text"></span>
<button class="delete-todo">削除</button>
<button class="update-todo">完了</button>
</li>
</template>
<script>
const todoInput = document.querySelector("#todo-input");
const addTodoButton = document.querySelector("#add-todo");
const todoList = document.querySelector("#todo-list");
const todoItemTemplate = document.querySelector("#todo-item-template");
// すべてのTODOを取得し、画面に表示する
function fetchTodos() {
fetch("http://localhost:3000/todos")
.then((response) => response.json())
.then((data) => {
todoList.innerHTML = "";
data.todos.forEach((todo) => {
const todoItem = todoItemTemplate.content.cloneNode(true);
const textElement = todoItem.querySelector(".todo-text");
const deleteButton = todoItem.querySelector(".delete-todo");
const updateButton = todoItem.querySelector(".update-todo");
textElement.textContent = todo.text;
updateButton.textContent = todo.completed ? "未完了" : "完了";
deleteButton.addEventListener("click", (event) => {
event.stopPropagation();
deleteTodo(todo);
});
updateButton.addEventListener("click", (event) => {
event.stopPropagation();
updateTodo(todo);
});
todoList.appendChild(todoItem);
});
});
}
// 新しいTODOを作成する
function addTodo() {
const newTodo = { text: todoInput.value, completed: false };
fetch("http://localhost:3000/todos", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newTodo),
}).then(() => {
fetchTodos();
todoInput.value = "";
});
}
// TODOを更新する
function updateTodo(todo) {
todo.completed = !todo.completed;
fetch(`http://localhost:3000/todos/${todo.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(todo),
}).then(fetchTodos);
}
// TODOを削除する
function deleteTodo(todo) {
fetch(`http://localhost:3000/todos/${todo.id}`, {
method: "DELETE",
}).then(fetchTodos);
}
addTodoButton.addEventListener("click", addTodo);
fetchTodos();
</script>
</body>
</html>
解説
HTML部分には <template>
タグを使用して、TODOアイテムのレイアウトを定義します。
これは非表示の要素で、その内容をJavaScriptで複製して表示するために使用します。
<template id="todo-item-template">
<li>
<span class="todo-text"></span>
<button class="delete-todo">削除</button>
<button class="update-todo">完了</button>
</li>
</template>
このテンプレート内の要素は、JavaScriptから参照できるようにクラス名を割り当てています。
const todoItem = todoItemTemplate.content.cloneNode(true);
const textElement = todoItem.querySelector(".todo-text");
const deleteButton = todoItem.querySelector(".delete-todo");
const updateButton = todoItem.querySelector(".update-todo");
新たなTODOアイテムを生成するために、テンプレートの内容を複製し(.content.cloneNode(true)
)、テキストとボタンをカスタマイズします。ボタンそれぞれには、対応する操作(削除や更新)を行うイベントリスナーを追加しています。
まとめ
この記事では、Express.jsを使用してバックエンドのAPIを作成し、そのAPIを使用してHTMLとJavaScriptでフロントエンドのTODOアプリを作成する方法を紹介しました。ここでは非常に基本的なアプリケーションを作成しましたが、これを基にしてさらに複雑なアプリケーションを作成することも可能です。