Mizui Note - ゼロから学ぶNoteCRUDチュートリアル
はじめに
このチュートリアルでは、LaravelとTailwind CSSを使ってノート管理アプリケーション(NoteCRUD)をゼロから作成します。プログラミングやLaravelの知識が全くない方でも理解できるよう、丁寧に解説します。
本記事の特徴
- Laravelプロジェクトの作成方法から解説
- データベース設定、マイグレーションの実行手順
- モデル・コントローラ・ルートの実装
- Bladeビュー(HTML)にTailwind CSSとJavaScriptを組み込む方法
- 動作確認方法までカバー
前提条件
- PHPとComposerがインストールされた環境
- データベース(MySQLなど)が使える環境
- ターミナル(コマンドプロンプト)を使えること
目次
- プロジェクト作成
- データベース設定
- マイグレーションの作成と実行
- モデルの作成
- コントローラの作成
- ルート設定
- ビューの作成(welcome.blade.php)
- 実行と動作確認
- まとめ
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