Mizui Note - ゼロから学ぶNoteCRUDチュートリアル

# Mizui Note - ゼロから学ぶNoteCRUDチュートリアル

## はじめに

このチュートリアルでは、LaravelとTailwind CSSを使ってノート管理アプリケーション（NoteCRUD）をゼロから作成します。プログラミングやLaravelの知識が全くない方でも理解できるよう、丁寧に解説します。

## 本記事の特徴

* Laravelプロジェクトの作成方法から解説
* データベース設定、マイグレーションの実行手順
* モデル・コントローラ・ルートの実装
* Bladeビュー（HTML）にTailwind CSSとJavaScriptを組み込む方法
* 動作確認方法までカバー

## 前提条件

* PHPとComposerがインストールされた環境
* データベース（MySQLなど）が使える環境
* ターミナル（コマンドプロンプト）を使えること

## 目次

1. プロジェクト作成
2. データベース設定
3. マイグレーションの作成と実行
4. モデルの作成
5. コントローラの作成
6. ルート設定
7. ビューの作成（welcome.blade.php）
8. 実行と動作確認
9. まとめ
---

## 1. プロジェクト作成

ターミナルで以下のコマンドを実行し、Laravelプロジェクトを作成します。

````bash
composer create-project laravel/laravel NoteCRUD
````
※ここにコードを貼り付けてください

プロジェクトディレクトリに移動します。


````bash
cd NoteCRUD
````

---

## 2. データベース設定
Laravelが接続するデータベース情報を`.env`ファイルに書き込みます。以下の項目を自分の環境に合わせて編集してください。

```
DB\_CONNECTION=mysql
DB\_HOST=127.0.0.1
DB\_PORT=3306
DB\_DATABASE=your\_database\_name
DB\_USERNAME=your\_username
DB\_PASSWORD=your\_password
````

---

## 3. マイグレーションの作成と実行
### ファイル: `database/migrations/2025_01_01_000000_create_notes_table.php`
以下の内容でファイルを作成してください。

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('notes', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('content');
            $table->string('group')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('notes');
    }
};
````

※ここにコードを貼り付けてください

マイグレーションを実行してテーブルを作成します。

```bash
php artisan migrate
```

---

## 4. モデルの作成

### ファイル: `app/Models/Note.php`

以下の内容でファイルを作成してください。

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Note extends Model
{
    protected $fillable = [
        'title',
        'content',
        'group'
    ];
}
```

※ここにコードを貼り付けてください

---

## 5. コントローラの作成

### ファイル: `app/Http/Controllers/NoteController.php`

以下の内容でファイルを作成してください。

```php
<?php

namespace App\Http\Controllers;

use App\Models\Note;
use Illuminate\Http\Request;

class NoteController extends Controller
{
    // 📘 普通の日本語: すべてのメモを表示する
    // 🟢 やさしい日本語: ぜんぶのメモを ひょうじする
    public function index()
    {
        // すべてのメモをデータベースから取得します。
        // Get all notes from the database
        $notes = Note::all();

        // 「welcome」ビューにデータを送って表示します。
        // Send notes to the "welcome" view
        return view('welcome', compact('notes'));
    }

    // 📘 普通の日本語: 新しいメモを作成する
    // 🟢 やさしい日本語: あたらしいメモを つくる
    public function store(Request $request)
    {
        // ユーザーが入力したデータを確認（バリデーション）します。
        // Validate user input
        $request->validate([
            'title' => 'required|max:255',            // タイトルは必須で255文字まで
            'content' => 'required',                  // 内容は必須
            'group' => 'nullable|string|max:255',     // グループは任意、255文字以内の文字列
        ]);

        // 新しいメモをデータベースに保存します。
        // Save the new note to the database
        Note::create($request->only('title', 'content', 'group'));

        // ホームページに戻ります。
        // Redirect to home page
        return redirect('/');
    }

    // 📘 普通の日本語: 既存のメモを更新する
    // 🟢 やさしい日本語: もっているメモを なおす（こうしんする）
    public function update(Request $request, Note $note)
    {
        // 入力データを確認（バリデーション）します。
        // Validate input
        $request->validate([
            'title' => 'required|max:255',            // タイトルは必須で255文字まで
            'content' => 'required',                  // 内容は必須
            'group' => 'nullable|string|max:255',     // グループは任意、255文字以内の文字列
        ]);

        // メモを更新します。
        // Update the note
        $note->update($request->only('title', 'content', 'group'));

        // ホームページに戻ります。
        // Redirect to home page
        return redirect('/');
    }

    // 📘 普通の日本語: メモを削除する
    // 🟢 やさしい日本語: メモを けす
    public function destroy(Note $note)
    {
        // メモを削除します。
        // Delete the note
        $note->delete();

        // 元のページに戻ります。
        // Go back to previous page
        return back();
    }
}

```

※ここにコードを貼り付けてください

---

## 6. ルート設定

### ファイル: `routes/web.php`

以下の内容でファイルを編集してください。

```php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\NoteController;

// ✅ 「/」というURLにアクセスしたとき、NoteControllerのindexメソッドを実行します。
// 📘 普通の日本語: メモの一覧を表示するルートです。
// 🟢 やさしい日本語: メモのリスト（いちらん）を みるための道（みち）です。
Route::get('/', [NoteController::class, 'index']);


// ✅ 「/notes」というURLにPOSTリクエストを送ると、NoteControllerのstoreメソッドが実行されます。
// 📘 普通の日本語: 新しいメモを保存するルートです。
// 🟢 やさしい日本語: あたらしいメモを つくるための道（みち）です。
Route::post('/notes', [NoteController::class, 'store'])->name('notes.store');


// ✅ 「/notes/{note}」にPUTリクエストを送ると、NoteControllerのupdateメソッドが実行されます。
// 📘 普通の日本語: 特定のメモを更新するルートです。{note} はメモのIDなどに置き換わります。
// 🟢 やさしい日本語: ひとつのメモを なおすための道（みち）です。{note}はメモの番号です。
Route::put('/notes/{note}', [NoteController::class, 'update'])->name('notes.update');


// ✅ 「/notes/{note}」にDELETEリクエストを送ると、NoteControllerのdestroyメソッドが実行されます。
// 📘 普通の日本語: 特定のメモを削除するルートです。{note} は削除したいメモのIDに置き換わります。
// 🟢 やさしい日本語: メモを けすための道（みち）です。{note}は けしたいメモの番号です。
Route::delete('/notes/{note}', [NoteController::class, 'destroy'])->name('notes.destroy');

```

※ここにコードを貼り付けてください

---

## 7. ビューの作成（resources/views/welcome.blade.php）

以下の内容を`resources/views/welcome.blade.php`として保存します。

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Mizui Note</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    /* アニメーション - フェードアウト＋上へスライド */
    @keyframes fadeSlideOut {
      0% { opacity: 1; transform: translateY(0); }
      100% { opacity: 0; transform: translateY(-50px); }
    }
    .fade-slide-out { animation: fadeSlideOut 1s ease forwards; }

    /* アニメーション - フェードイン＋下からスライド */
    @keyframes fadeIn {
      from { opacity: 0; transform: translateY(20px); }
      to { opacity: 1; transform: translateY(0); }
    }
    .fade-in { animation: fadeIn 0.7s ease forwards; }

    /* カスタムセレクトボックスのスタイル */
    .custom-select {
      -webkit-appearance: none;
      -moz-appearance: none;
      appearance: none;
      padding: 0.5rem 2.5rem 0.5rem 1rem;
      background-color: white;
      border: 1px solid #ddd;
      border-radius: 0.375rem;
      font-size: 1rem;
      font-weight: 500;
      color: #4b5563;
      position: relative;
      transition: border-color 0.3s ease;
    }
    .custom-select:hover {
      border-color: #4f46e5;
    }
    .custom-select:focus {
      outline: none;
      border-color: #4f46e5;
    }
    .custom-select::after {
      content: ' \25BC';
      position: absolute;
      top: 50%;
      right: 0.75rem;
      transform: translateY(-50%);
      pointer-events: none;
      font-size: 1.25rem;
      color: #6b7280;
    }
  </style>
</head>
<body class="bg-gradient-to-br from-purple-600 to-purple-900 min-h-screen text-gray-100">

  <!-- ウェルカム画面 -->
  <div id="welcome" class="flex flex-col items-center justify-center min-h-screen transition-all">
    <h1 class="text-4xl font-extrabold mb-4">Welcome to Note</h1>
    <p class="text-lg mb-8 text-purple-100 hidden sm:block">Your personal note-taking application.</p>
  </div>

  <!-- ナビゲーションバー -->
  <nav id="navbar" class="hidden fixed top-0 left-0 w-full bg-white text-purple-800 shadow-md py-4 px-6 flex justify-between items-center z-50">
    <h1 class="text-2xl font-bold tracking-wide font-serif">Mizui <span class="text-purple-600">Note</span></h1>
    <!-- ノートのフィルター -->
    <select id="filterSelect" class="custom-select" onchange="filterNotes()">
      <option value="">All Notes</option>
      <option value="Personal">Personal</option>
      <option value="Work">Work</option>
      <option value="Ideas">Ideas</option>
    </select>
  </nav>

  <!-- ノート作成・編集モーダル -->
  <div id="modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
    <div class="bg-white text-gray-800 p-6 rounded-2xl w-[95%] max-w-md shadow-lg relative">
      <h2 class="text-2xl font-bold mb-4 text-purple-700" id="modalTitle">Create Note</h2>
      <form id="noteForm" method="POST">
        <!-- メソッド切り替え用の隠しフィールド -->
        <input type="hidden" name="_method" value="POST" id="formMethod">
        <input type="hidden" name="_token" value="{{ csrf_token() }}">
        <input type="hidden" name="id" id="noteId">

        <!-- タイトルとカテゴリ入力 -->
        <div class="flex gap-2 mb-3 items-center">
          <input name="title" id="titleInput" placeholder="Title" class="w-full text-xl font-semibold placeholder-gray-400 focus:outline-none bg-transparent" required>
          <select name="group" id="groupSelect" class="custom-select">
            <option value="">Group</option>
            <option value="Personal">Personal</option>
            <option value="Work">Work</option>
            <option value="Ideas">Ideas</option>
          </select>
        </div>

        <!-- ノートの本文 -->
        <textarea name="content" id="contentInput" placeholder="Content" class="w-full text-gray-800 placeholder-gray-500 bg-transparent resize-none focus:outline-none h-32 mb-4" required></textarea>

        <!-- ボタン：キャンセル / 保存 -->
        <div class="flex justify-end gap-2">
          <button type="button" onclick="toggleModal()" class="bg-gray-300 hover:bg-gray-400 text-gray-800 px-4 py-2 rounded">Cancel</button>
          <button type="submit" class="bg-purple-700 hover:bg-purple-800 text-white px-4 py-2 rounded" id="submitBtn">Save</button>
        </div>
      </form>
    </div>
  </div>

  <!-- ノート追加フロートボタン -->
  <button onclick="openCreateModal()" class="fixed bottom-8 right-8 bg-purple-700 hover:bg-purple-800 text-white w-20 h-20 p-4 rounded-full shadow-lg text-3xl z-40">+</button>

  <!-- ノート一覧グリッド -->
  <div id="grid" class="hidden p-6 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 pt-28">
    @foreach($notes as $note)
      <div data-group="{{ $note->group }}" class="note-card cursor-pointer bg-white text-gray-800 p-5 rounded-xl shadow-lg border-l-4 border-purple-600 transition hover:scale-[1.02]" onclick="openEditModal('{{ $note->id }}', '{{ $note->title }}', '{{ $note->content }}', '{{ $note->group }}')">
        <h3 class="text-xl font-bold text-purple-800 mb-2">{{ $note->title }}</h3>
        <p class="text-gray-700 mb-2">{{ $note->content }}</p>
        <div class="flex items-center justify-between">
          @if($note->group)
            <span class="inline-block bg-purple-100 text-purple-700 text-xs px-2 py-1 rounded-full">{{ $note->group }}</span>
          @endif
          <!-- 削除ボタンに確認ダイアログを追加 -->
          <form action="{{ route('notes.destroy', $note->id) }}" method="POST" onclick="event.stopPropagation();" class="inline-block" onsubmit="return confirm('Are you sure you want to delete this note?')">
            @csrf
            @method('DELETE')
            <button type="submit" class="text-red-500 hover:text-red-700 text-sm">Delete</button>
          </form>
        </div>
      </div>
    @endforeach
  </div>

  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const welcome = document.getElementById('welcome');
      const navbar = document.getElementById('navbar');
      const grid = document.getElementById('grid');
  
      // ローカルストレージからウェルカム画面を表示するかどうかの情報を取得
      const hasVisited = localStorage.getItem('hasVisited');
  
      // 初回アクセスの場合：トランジションを表示
      if (!hasVisited) {
        // いずれかのキーが押されたらメイン画面へ移動
        window.addEventListener('keydown', () => {
          transitionToMain();
        });
  
        // 2秒後に自動的にメイン画面へ移動
        setTimeout(transitionToMain, 2000);
      } else {
        // 2回目以降のアクセス：ウェルカム画面をスキップ
        welcome.remove(); // ウェルカム画面を削除
        navbar.classList.remove('hidden'); // ナビゲーションバーを表示
        grid.classList.remove('hidden'); // ノートグリッドを表示
      }
  
      // ウェルカム画面からメイン画面への切り替え処理
      function transitionToMain() {
        // トランジションアニメーションを付与
        welcome.classList.add('fade-slide-out');
  
        // アニメーション終了後にDOMを更新
        setTimeout(() => {
          welcome.remove(); // ウェルカム画面を完全に削除
          navbar.classList.remove('hidden'); // ナビゲーションバーを表示
          navbar.classList.add('fade-in');   // フェードインアニメーション
          grid.classList.remove('hidden');   // ノートグリッドを表示
          grid.classList.add('fade-in');     // フェードインアニメーション
  
          // ローカルストレージに訪問済みフラグを保存
          localStorage.setItem('hasVisited', 'true');
        }, 1000); // アニメーション時間と一致させる
      }
    });
  
    // モーダルの表示・非表示を切り替える関数
    function toggleModal() {
      document.getElementById('modal').classList.toggle('hidden');
    }
  
    // 新規作成用モーダルを開く関数
    function openCreateModal() {
      document.getElementById('modalTitle').textContent = 'Create Note';
      document.getElementById('submitBtn').textContent = 'Save';
      document.getElementById('noteForm').action = "{{ route('notes.store') }}";
      document.getElementById('formMethod').value = 'POST';
      document.getElementById('noteId').value = '';
      document.getElementById('titleInput').value = '';
      document.getElementById('contentInput').value = '';
      document.getElementById('groupSelect').value = '';
      toggleModal();
    }
  
    // 既存ノートの編集用モーダルを開く関数
    function openEditModal(id, title, content, group) {
      document.getElementById('modalTitle').textContent = 'Edit Note';
      document.getElementById('submitBtn').textContent = 'Update';
      document.getElementById('noteForm').action = `/notes/${id}`;
      document.getElementById('formMethod').value = 'PUT';
      document.getElementById('noteId').value = id;
      document.getElementById('titleInput').value = title;
      document.getElementById('contentInput').value = content;
      document.getElementById('groupSelect').value = group;
      toggleModal();
    }
  
    // ノートのカテゴリ（グループ）でフィルタリングを行う関数
    function filterNotes() {
      const filter = document.getElementById('filterSelect').value; // 選択されたカテゴリ
      const notes = document.querySelectorAll('.note-card'); // すべてのノートカードを取得
  
      notes.forEach(note => {
        // フィルター条件に一致するノートのみ表示
        if (filter === '' || note.getAttribute('data-group') === filter) {
          note.classList.remove('hidden');
        } else {
          note.classList.add('hidden');
        }
      });
    }
  </script>  
</body>
</html>
```

※ここにコードを貼り付けてください

---

## 8. 実行と動作確認

サーバーを起動して動作を確認します。

```bash
php artisan serve
```

ブラウザで `http://127.0.0.1:8000` にアクセスし、ノートの作成・編集・削除が動作することを確認してください。

---

## 9. まとめ

これでLaravelとTailwind CSSを使ったシンプルなノート管理アプリが完成しました。初心者でも動くアプリを作れることを体験できたはずです。

GitHub
```
https://github.com/mizukaze554/NoteCRUD
```