1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

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プロジェクトを作成します。

composer create-project laravel/laravel NoteCRUD

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

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

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

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');
    }
};

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

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

php artisan migrate

4. モデルの作成

ファイル: app/Models/Note.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

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

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として保存します。

<!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. 実行と動作確認

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

php artisan serve

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


9. まとめ

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

GitHub

https://github.com/mizukaze554/NoteCRUD
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?