こんにちは!tenjiprogrammingです。
今回はToDoアプリの作成を解説します。
前回の四択クイズと同様に詳細すぎる解説はしません。4択クイズの解説記事はこちら↓
今回もコードは全て載せています。コピペでも模写でもいいので実際に自分の環境で作ってみてください!
今回作るもの
今回作るToDoアプリは非常にシンプルなものです。まず実際に画面を紹介します。
タスクと納期を入力し追加ボタンを押すと、下にタスクが表示されます。
タスクを終えた際に完了ボタンを押すとそのタスクは非表示になります。
また今回データベースにMySQLを用いていますが、ローカルサーバで動かしています。
なので自分専用のToDoアプリです。
フロントエンドはJavaScriptとReact、バックエンドはnodeで書いています。
では早速解説に入っていきましょう!
データベースの準備
MySQLの準備は以下の記事を参考にしてください。Progate様が公開しているページです。
・windows向け(外部リンクに移ります。)
・mac向け(外部リンクに移ります。)
MySQLサーバをローカルで動かす準備ができたらデータベースとテーブルを作成します。
データベース名やテーブル名は任意で大丈夫です。
今回は、
データベース名 : todo
テーブル名 : tasks
としました!
テーブルのカラムは次のようにしました。
一応create文も載せておきます。
CREATE TABLE tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
completed BOOLEAN DEFAULT false,
deadline DATE
);
これで今回使うデータベースとテーブルの準備は終了です。
後ほどMySQLのユーザ名やパスワードは使うのでメモしておくのをおすすめします。
バックエンドの実装
次にバックエンドを実装します。
バックエンドは実際にデータベースに値を登録したり、値を取得したりする部分です。
まずはコードの完成形を載せます。
// app.js
const express = require('express');
const cors = require('cors');
const mysql = require('mysql');
const app = express();
app.use(express.json());
app.use(cors());
const connection = mysql.createConnection({
host: '127.0.0.1', // localhostと書けばいいことが多い
user: 'db_username', // データベースのユーザー名
password: 'db_password', // データベースのパスワード
database: 'todo', // データベース名
});
connection.connect((err) => {
if (err) {
console.error('MySQL接続エラー:', err);
} else {
console.log('MySQLに接続しました。');
}
});
const port = 3001;
app.get('/api/tasks', (req, res) => {
const getTasksQuery = 'SELECT * FROM tasks';
connection.query(getTasksQuery, (err, results) => {
if (err) {
console.error('MySQLエラー:', err);
return res.status(500).json({ error: 'タスクの取得に失敗しました。' });
}
res.status(200).json(results);
});
});
app.post('/api/tasks', (req, res) => {
const { title, deadline } = req.body;
const insertQuery = 'INSERT INTO tasks (title, completed, deadline) VALUES (?, ?, ?)';
connection.query(insertQuery, [title, false, deadline], (err, results) => {
if (err) {
console.error('MySQLエラー:', err);
return res.status(500).json({ error: 'タスクの追加に失敗しました。' });
}
res.status(201).json({ id: results.insertId, title, completed: false, deadline });
});
});
app.put('/api/tasks/:id/complete', (req, res) => {
const taskId = req.params.id;
const updateQuery = 'UPDATE tasks SET completed = ? WHERE id = ?';
connection.query(updateQuery, [true, taskId], (err, results) => {
if (err) {
console.error('MySQLエラー:', err);
return res.status(500).json({ error: 'タスクの完了状態の更新に失敗しました。' });
}
if (results.affectedRows === 0) {
return res.status(404).json({ error: '指定されたタスクが見つかりません。' });
}
res.status(200).json({ id: taskId, completed: true });
});
});
app.listen(port, () => {
console.log(`サーバーがポート${port}で起動しました。`);
});
まずバックエンド用のディレクトリを用意してください。
名前はなんでも大丈夫です。そこでnode.jsのセットアップをします。
mkdir todo-backend
cd todo-backend
npm init -y
次に必要なパッケージをインストールします。
npm install express mysql body-parser cors --save
任意のファイルを作成し(今回の例だとapp.js)、そこに最初に紹介したコードを記述します。
バックエンドはこれで完成です。一応少しだけコードの解説をします。
解説
// app.js
const express = require('express');
const cors = require('cors');
const mysql = require('mysql');
const app = express();
app.use(express.json());
app.use(cors());
Expressアプリケーションを設定し、JSON形式のリクエストボディの解析とCORSの設定を行っています。
これらの設定を行うことで、フロントエンドとバックエンドが連携するための土台が整います。
const connection = mysql.createConnection({
host: '127.0.0.1', // localhostと書けばいいことが多い
user: 'db_username', // データベースのユーザー名
password: 'db_password', // データベースのパスワード
database: 'todo', // データベース名
});
ここでデータベースの情報を記述します。ここが違うと上手くデータベースに接続できないので注意してください。
ホスト名は基本的にはlocalhostと書けば大丈夫ですが、homebrewを使ってMySQLを使用している場合は127.0.0.1と記述しないとうまくできないことがあるので自分の環境に合わせて書き換えてください。
connection.connect((err) => {
if (err) {
console.error('MySQL接続エラー:', err);
} else {
console.log('MySQLに接続しました。');
}
});
MySQLへの接続を行い失敗した場合のエラーハンドリングも実装しています。
const port = 3001;
バックエンドのサーバを立ち上げるポート番号を指定しています。
フロントエンド用のサーバと異なるポートであれば何番でも大丈夫です。
app.get('/api/tasks', (req, res) => {
const getTasksQuery = 'SELECT * FROM tasks';
connection.query(getTasksQuery, (err, results) => {
if (err) {
console.error('MySQLエラー:', err);
return res.status(500).json({ error: 'タスクの取得に失敗しました。' });
}
res.status(200).json(results);
});
});
このコードは、HTTP GETリクエストが /api/tasks
に送信されたときにMySQLデータベースから全てのタスクを取得して、それをJSON形式で返すエンドポイントを定義しています。
データベースからタスクが欲しい時は、フロントエンドからapi/tasksにGETリクエストを送ることになります。
pp.post('/api/tasks', (req, res) => {
const { title, deadline } = req.body;
const insertQuery = 'INSERT INTO tasks (title, completed, deadline) VALUES (?, ?, ?)';
connection.query(insertQuery, [title, false, deadline], (err, results) => {
if (err) {
console.error('MySQLエラー:', err);
return res.status(500).json({ error: 'タスクの追加に失敗しました。' });
}
res.status(201).json({ id: results.insertId, title, completed: false, deadline });
});
});
このコードは、HTTP POSTリクエストが /api/tasks
に送信されたときにリクエストボディから取得したタスクの情報をMySQLデータベースに追加し、追加されたタスクの情報をJSON形式でクライアントに返すエンドポイントを定義しています。
タスクの追加を行うときは、フロントエンドからこのエンドポイントにPOSTリクエストを送信します。
app.put('/api/tasks/:id/complete', (req, res) => {
const taskId = req.params.id;
const updateQuery = 'UPDATE tasks SET completed = ? WHERE id = ?';
connection.query(updateQuery, [true, taskId], (err, results) => {
if (err) {
console.error('MySQLエラー:', err);
return res.status(500).json({ error: 'タスクの完了状態の更新に失敗しました。' });
}
if (results.affectedRows === 0) {
return res.status(404).json({ error: '指定されたタスクが見つかりません。' });
}
res.status(200).json({ id: taskId, completed: true });
});
});
このコードは、HTTP PUTリクエストが /api/tasks/:id/complete
に送信されたときに指定されたタスクの完了状態をMySQLデータベースで更新し、更新後のタスクの情報をJSON形式でクライアントに返すエンドポイントを定義しています。
最初に見せた画面の完了ボタンを押した時にこのエンドポイントにPUTリクエストを送信します。
app.listen(port, () => {
console.log(`サーバーがポート${port}で起動しました。`);
});
最後に先ほど指定したポート番号でサーバを起動させます。
バックエンドのコードの解説は以上です。次はフロントエンドを実装しましょう。
フロントエンドの実装
フロントエンド用のディレクトリはバックエンドと並列の場所に用意します。
用意する際はcreate-react-appで作成します。
npx create-react-app todo
これでreactの実行環境ができます。ディレクトリ構造は以下のとおりです。不要なファイルは削除しています。
コードを書くのはApp.jsとAddTaskForm.jsです。
まずは完成形のコードです。
// src/App.js
import React, { useEffect, useState } from 'react';
import AddTaskForm from './components/AddTaskForm';
const App = () => {
const [tasks, setTasks] = useState([]);
const getIncompleteTasks = async () => {
try {
const response = await fetch('http://localhost:3001/api/tasks');
const data = await response.json();
setTasks(data);
} catch (error) {
console.error('エラー:', error);
}
};
useEffect(() => {
getIncompleteTasks();
}, []);
const handleTaskAdded = (newTask) => {
setTasks([...tasks, newTask]);
};
const handleTaskCompletion = (taskId) => {
fetch(`http://localhost:3001/api/tasks/${taskId}/complete`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => response.json())
.then((data) => {
const updatedTasks = tasks.map((task) =>
task.id === data.id ? { ...task, completed: true } : task
);
setTasks(updatedTasks);
window.location.reload();
})
.catch((error) => console.error('エラー:', error));
};
const filteredTasks = tasks.filter((task) => !task.completed);
return (
<div>
<h1>ToDoアプリ</h1>
<AddTaskForm onTaskAdded={handleTaskAdded} />
<ul>
{filteredTasks.map((task) => (
<li key={task.id}>
{task.title} (納期: {task.deadline})
<button onClick={() => handleTaskCompletion(task.id)}>完了</button>
</li>
))}
</ul>
</div>
);
};
export default App;
// src/components/AddTaskForm.js
import React, { useState } from 'react';
const AddTaskForm = ({ onTaskAdded }) => {
const [title, setTitle] = useState('');
const [deadline, setDeadline] = useState('');
const handleTitleChange = (e) => {
setTitle(e.target.value);
};
const handleDeadlineChange = (e) => {
setDeadline(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
fetch('http://localhost:3001/api/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ title, deadline }),
})
.then((response) => response.json())
.then((data) => {
onTaskAdded(data);
setTitle('');
setDeadline('');
})
.catch((error) => console.error('エラー:', error));
};
return (
<form onSubmit={handleSubmit}>
<label>
タスク:
<input type="text" value={title} onChange={handleTitleChange} />
</label>
<label>
納期:
<input type="date" value={deadline} onChange={handleDeadlineChange} />
</label>
<button type="submit">追加</button>
</form>
);
};
export default AddTaskForm;
画面上にタスクを入力する欄と納期を選択するdate typeの入力フォームを用意します。
追加ボタンが押されたらPOSTリクエストを送信し、完了ボタンが押されたらPUTリクエストを送信します。
ポート番号はバックエンドのサーバが起動しているポート番号にしてください。
以上でフロントエンドのコードが完成です。
実行する
まずはMySQLサーバを起動します。ターミナルなどで
mysql -u 'ユーザ名' -p
を実行することでMySQLが起動します。
次にバックエンドのサーバを起動します。バックエンド用のディレクトリに移動したら、
node app.js
を実行します。
このような表示が出れば成功です。
最後にフロントエンドのサーバを起動します。フロントエンド用のディレクトリで
npm start
を実行します。正常に起動すると、ToDoアプリが開かれるはずです。
出るかもしれないエラー
MySQLサーバに接続できない系のエラーが出た場合
・MySQLのユーザ名などの接続情報が間違えている
・バージョンによるエラー
のどちらかであることが多いです。
前者の場合は正しい情報に修正すれば大丈夫です。
後者の場合はMySQLのターミナル上で
ALTER USER 'your_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';
を実行してください。(ユーザ名とパスワードは自分のものに変えてください。)
その後もう一度バックエンドを起動すればうまくいくはずです。
まとめ
今回はToDoリストの作り方を、サンプルコードを交えて解説しました。
このコードをアレンジして自分オリジナルのToDoリストを作ってみてください!
当ブログではJavaScriptの練習問題を無料で公開しているのでぜひ挑戦してみてください。
コメント