🗂

【Task-PRG】ディレクトリ構造の確認と、タスク管理機能の実装手順

2024/05/20に公開

はじめに

https://zenn.dev/tomo0108/books/92b1b7a60c7737
アプリ開発全体の流れは、こちらの本にまとめています。

タスク管理アプリのディレクトリ構造と実装手順

『Task-PRG』のディレクトリ構造を確認し、実装手順を説明します。

ステップ1: ディレクトリ構造の確認

以下が現時点での『Task-PRG』プロジェクトのディレクトリ構造です。

ディレクトリ構造
/task-rpg
├── .github
│   └── workflows
│       └── deploy.yml
├── public
│   ├── icons
│   ├── index.html
│   ├── main.js
│   └── manifest.json
├── script
│   └── deploy_script.sh
├── styles
│   └── main.css
├── index.js
├── models
│   └── task.js
├── routes
│   └── tasks.js
├── package.json
└── package-lock.json

ステップ2: タスク管理アプリの機能実装手順

2.1 index.jsの設定

index.jsファイルにサーバーの設定を記述します。

index.js
const express = require('express');
const mysql = require('mysql2');
const taskRouter = require('./routes/tasks');

const app = express();
app.use(express.json());
app.use(express.static('public'));

// RDS接続の設定
const connection = mysql.createConnection({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME
});

connection.connect(err => {
    if (err) {
        console.error('Error connecting to the database:', err);
        return;
    }
    console.log('Connected to the MySQL database');
});

// タスクルートの使用
app.use('/tasks', taskRouter);

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

2.2 モデルの設定

models/task.jsファイルを作成し、タスクモデルを設定します。

task.js
const connection = require('../db');

// タスクテーブルを作成するクエリ
const createTableQuery = `
    CREATE TABLE IF NOT EXISTS tasks (
        id INT AUTO_INCREMENT PRIMARY KEY,
        title VARCHAR(255) NOT NULL,
        level INT NOT NULL,
        completed BOOLEAN DEFAULT false
    )
`;

// タスクテーブルを作成
connection.query(createTableQuery, (err, results) => {
    if (err) {
        console.error('Error creating tasks table:', err);
        return;
    }
    console.log('Tasks table created or already exists');
});

module.exports = connection;

2.3 ルートの設定

routes/tasks.jsファイルを作成し、CRUDルートを設定します。

tasks.js
const express = require('express');
const router = express.Router();
const connection = require('../models/task');

// 全てのタスクを取得
router.get('/', (req, res) => {
    connection.query('SELECT * FROM tasks', (err, results) => {
        if (err) {
            res.status(500).json({ message: err.message });
            return;
        }
        res.json(results);
    });
});

// 新しいタスクを作成
router.post('/', (req, res) => {
    const { title, level } = req.body;
    const query = 'INSERT INTO tasks (title, level) VALUES (?, ?)';
    connection.query(query, [title, level], (err, results) => {
        if (err) {
            res.status(400).json({ message: err.message });
            return;
        }
        res.status(201).json({ id: results.insertId, title, level, completed: false });
    });
});

// 特定のタスクを取得
router.get('/:id', (req, res) => {
    const query = 'SELECT * FROM tasks WHERE id = ?';
    connection.query(query, [req.params.id], (err, results) => {
        if (err) {
            res.status(500).json({ message: err.message });
            return;
        }
        if (results.length === 0) {
            res.status(404).json({ message: 'Cannot find task' });
            return;
        }
        res.json(results[0]);
    });
});

// タスクを更新
router.patch('/:id', (req, res) => {
    const { title, level, completed } = req.body;
    const query = 'UPDATE tasks SET title = ?, level = ?, completed = ? WHERE id = ?';
    connection.query(query, [title, level, completed, req.params.id], (err, results) => {
        if (err) {
            res.status(400).json({ message: err.message });
            return;
        }
        res.json({ message: 'Task updated' });
    });
});

// タスクを削除
router.delete('/:id', (req, res) => {
    const query = 'DELETE FROM tasks WHERE id = ?';
    connection.query(query, [req.params.id], (err, results) => {
        if (err) {
            res.status(500).json({ message: err.message });
            return;
        }
        res.json({ message: 'Task deleted' });
    });
});

module.exports = router;

2.4 フロントエンドの設定

public/index.htmlファイルに基本的なUIを設定します。

index.html
<!DOCTYPE html>
<html lang="jp">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Task-RPG</title>
    <link rel="manifest" href="/manifest.json">
    <link rel="stylesheet" href="/styles/main.css">
</head>
<body>
    <h1>Welcome to Task RPG!</h1>
    <div id="task-list"></div>
    <form id="task-form">
        <input type="text" id="title" placeholder="Task Title">
        <input type="number" id="level" placeholder="Task Level">
        <button type="submit">Add Task</button>
    </form>
    <script src="/main.js"></script>
</body>
</html>

public/main.jsファイルにタスクの取得、表示、作成、更新、削除機能を記述します。

main.js
document.addEventListener('DOMContentLoaded', () => {
    const taskList = document.getElementById('task-list');
    const taskForm = document.getElementById('task-form');

    // タスクを取得して表示する関数
    async function fetchTasks() {
        const response = await fetch('/tasks');
        const tasks = await response.json();
        taskList.innerHTML = '';
        tasks.forEach(task => {
            const taskItem = document.createElement('div');
            taskItem.className = 'task-item';
            taskItem.innerHTML = `
                <strong>Title:</strong> ${task.title}<br>
                <strong>Level:</strong> ${task.level}<br>
                <strong>Completed:</strong> ${task.completed ? 'Yes' : 'No'}

<br>
                <button onclick="completeTask(${task.id})">Complete</button>
                <button onclick="deleteTask(${task.id})">Delete</button>
            `;
            taskList.appendChild(taskItem);
        });
    }

    // 新しいタスクを作成する関数
    taskForm.addEventListener('submit', async (e) => {
        e.preventDefault();
        const title = document.getElementById('title').value;
        const level = document.getElementById('level').value;

        const response = await fetch('/tasks', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ title, level })
        });

        if (response.ok) {
            fetchTasks();
        }
    });

    // タスクを完了する関数
    window.completeTask = async (id) => {
        const response = await fetch(`/tasks/${id}`, {
            method: 'PATCH',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ completed: true })
        });

        if (response.ok) {
            fetchTasks();
        }
    };

    // タスクを削除する関数
    window.deleteTask = async (id) => {
        const response = await fetch(`/tasks/${id}`, {
            method: 'DELETE'
        });

        if (response.ok) {
            fetchTasks();
        }
    };

    // 初回ロード時にタスクを取得して表示
    fetchTasks();
});

public/styles/main.cssファイルにスタイルを記述します。

main.css
body {
    font-family: 'Arial', sans-serif;
    background-color: #f0f0f0;
    margin: 0;
    padding: 20px;
}

h1 {
    text-align: center;
}

#task-list {
    margin-top: 20px;
}

.task-item {
    background-color: #fff;
    padding: 10px;
    margin-bottom: 10px;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

#task-form {
    display: flex;
    flex-direction: column;
    align-items: center;
}

#task-form input, #task-form button {
    margin: 5px 0;
    padding: 10px;
    border-radius: 5px;
    border: 1px solid #ccc;
}

#task-form button {
    background-color: #007bff;
    color: white;
    cursor: pointer;
}

#task-form button:hover {
    background-color: #0056b3;
}

ステップ3: デプロイ手順

  1. GitHubリポジトリの設定:
    以下の手順に従い、Github Actionsの設定とdeploy.ymlの作成を行います。

https://zenn.dev/tomo0108/articles/dddc88d27d06d1

  1. デプロイの実行:
    GitHub Actionsが自動的にトリガーされ、EC2インスタンスにアプリケーションがデプロイされます。
bashコマンド
git add .
git commit -m "Add GitHub Actions for EC2 deployment"
git push origin main

以上で、EC2インスタンス上で動作し、AWS RDSをバックエンドとして利用するタスク管理機能のデモページが完成しました。

Discussion