githubのcodespaces laravel11 VS CODE でtodoアプリづくり

参考にしたサイト
https://www.seplus.jp/dokushuzemi/blog/2023/01/tutorial_github_codespaces.html
https://b-risk.jp/blog/2022/08/laravel
今回、codespaces テンプレートは、

を使用し、laravel11、postgresql 構成でVS CODE 上に環境作りました。

VS CODE 上のターミナルで、
php artisan serve
http://127.0.0.1:8000

Laravelの初期設定
.env
APP_NAME=LaravelTest
...
APP_TIMEZONE=Asia/Tokyo
APP_LOCALE=ja
APP_FALLBACK_LOCALE=ja
APP_FAKER_LOCALE=ja_JP
VS CODE の初期設定

接続情報の設定
ホスト:db、db・ユーザー:laravel/laravel、ポート:5432

サンプルアプリ
モデル、テーブル作成
php artisan make:model TodoList -mc
public function up()
{
Schema::create('todo_lists', function (Blueprint $table) {
$table->id();
$table->string('name', 100);
$table->timestamps();
});
}
php artisan migrate
テストデータ作成
php artisan make:seeder TodoListSeeder
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class TodoListSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('todo_lists')->insert(
[
[
'name' => 'テスト1',
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'テスト2',
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'テスト3',
'created_at' => now(),
'updated_at' => now(),
],
]
);
}
}
database\seeders\DatabaseSeeder.php
public function run()
{
$this->call([
TodoListSeeder::class
]);
}
php artisan db:seed –class=TodoListSeeder
コントローラ作成
app\Http\Controllers\TodoListController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\TodoList;
class TodoListController extends Controller
{
public function index(Request $request)
{
$todo_lists = TodoList::all();
return view('todo_list.index', ['todo_lists' => $todo_lists]);
}
}
ビュー作成
resources\views\todo_list\index.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>テスト</title>
</head>
<body>
@if ($todo_lists->isNotEmpty())
<ul>
@foreach ($todo_lists as $item)
<li>
{{ $item->name }}
</li>
@endforeach
</ul>
@endif
</body>
</html>
ルーティング設定
routes\web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TodoListController;
Route::get('/', function () {
return view('welcome');
});
Route::get('/list', [TodoListController::class, 'index']);
TailwindCSS 導入
VS CODE ターミナルで
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
ファイルが2つ作成。postcss.config.jsとtailwind.config.js
tailwind.config.jsを編集
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./resources/**/*.blade.php",
"./resources/**/*.js",
"./resources/**/*.vue",
],
theme: {
extend: {},
},
plugins: [],
}
resources\css\app.css 編集
@tailwind base;
@tailwind components;
@tailwind utilities;
npm run dev ※php artisan serveも実行中に。
http://127.0.0.1:5173/

http://127.0.0.1:8000/list

tasksテーブル作成
VS CODE ターミナルで、
php artisan make:migration create_tasks_table
database/migrations/日付_create_tasks_table.phpの編集
/** * Run the migrations. */ public function up(): void { Schema::create(‘tasks’, function (Blueprint $table) { $table->id(); $table->string(‘name’, 100); $table->boolean(‘status’)->default(false); $table->timestamp(‘updated_at’)->useCurrent()->nullable(); $table->timestamp(‘created_at’)->useCurrent()->nullable(); // $table->timestamps(); }); }
php artisan migrate

Task モデル作成
php artisan make:model Task
コントローラー作成。リソースコントローラーを作成
php artisan make:controller TaskController –resource
web.php を編集
use App\Http\Controllers\TaskController;
Route::resource('tasks', TaskController::class);
現在のルーティングを確認
php artisan route:list

ビュー作成
resources/views/tasks/index.blade.php の作成
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Todo</title>
@vite('resources/css/app.css')
</head>
<body class="flex flex-col min-h-[100vh]">
<header class="bg-slate-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6">
<div class="py-6">
<p class="text-white text-xl">Todoアプリ</p>
</div>
</div>
</header>
<main class="grow">
<div class="max-w-7xl mx-auto px-4 sm:px-6">
<div class="py-[100px]">
<p class="text-2xl font-bold text-center">今日は何する?</p>
<form action="/tasks" method="post" class="mt-10">
@csrf
<div class="flex flex-col items-center">
<label class="w-full max-w-3xl mx-auto">
<input
class="placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-4 pl-4 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm"
placeholder="洗濯物をする..." type="text" name="task_name" />
</label>
<button type="submit" class="mt-8 p-4 bg-slate-800 text-white w-full max-w-xs hover:bg-slate-900 transition-colors">
追加する
</button>
</div>
</form>
</div>
</div>
</main>
<footer class="bg-slate-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6">
<div class="py-4 text-center">
<p class="text-white text-sm">Todoアプリ</p>
</div>
</div>
</footer>
</body>
</html>
TaskController.php 編集
public function index()
{
return view('tasks.index');
}
php artisan serve
npm run dev
http://127.0.0.1:8000/tasks

タスクを登録する
TaskController.php 編集。ddメソッドを使いデバッグテスト。
public function store(Request $request)
{
$task_name = $request->input('task_name');
dd($task_name);
}
テスト入力。

OK。

では、実装します。
TaskController.phpに、
...
use App\Models\Task;
...
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//モデルをインスタンス化
$task = new Task;
//モデル->カラム名 = 値 で、データを割り当てる
$task->name = $request->input('task_name');
//データベースに保存
$task->save();
//リダイレクト
return redirect('/tasks');
}
先ほどのテスト入力。

DB確認。

バリデーション
Validatorクラスを利用したいので、TaskController.phpに以下を追記
...
use Illuminate\Support\Facades\Validator;
...
public function store(Request $request)
{
$rules = [
'task_name' => 'required|max:100',
];
$messages = ['required' => '必須項目です', 'max' => '100文字以下にしてください。'];
Validator::make($request->all(), $rules, $messages)->validate();
//モデルをインスタンス化
$task = new Task;
//モデル->カラム名 = 値 で、データを割り当てる
$task->name = $request->input('task_name');
//データベースに保存
$task->save();
//リダイレクト
return redirect('/tasks');
}
index.blade.phpのinputタグの下に、エラーメッセージ表示を追加する。
<input
class="placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-4 pl-4 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm"
placeholder="洗濯物をする..." type="text" name="task_name" />
@error('task_name')
<div class="mt-3">
<p class="text-red-500">
{{ $message }}
</p>
</div>
@enderror
</label>


OK。
最後にまとめて「タスクを表示する、編集する、完了する、削除する。」
TaskController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Task;
use Illuminate\Support\Facades\Validator;
class TaskController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
// $tasks = Task::all();
$tasks = Task::where('status', false)->get();
return view('tasks.index', compact('tasks'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* store
*/
public function store(Request $request)
{
$rules = [
'task_name' => 'required|max:100',
];
$messages = ['required' => '必須項目です', 'max' => '100文字以下にしてください。'];
Validator::make($request->all(), $rules, $messages)->validate();
//モデルをインスタンス化
$task = new Task;
//モデル->カラム名 = 値 で、データを割り当てる
$task->name = $request->input('task_name');
//データベースに保存
$task->save();
//リダイレクト
return redirect('/tasks');
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
$task = Task::find($id);
return view('tasks.edit', compact('task'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
// dd($request->status);//追記
//「編集する」ボタンをおしたとき
if ($request->status === '9') {
$rules = [
'task_name' => 'required|max:100',
];
$messages = ['required' => '必須項目です', 'max' => '100文字以下にしてください。'];
Validator::make($request->all(), $rules, $messages)->validate();
//該当のタスクを検索
$task = Task::find($id);
//モデル->カラム名 = 値 で、データを割り当てる
$task->name = $request->input('task_name');
//データベースに保存
$task->save();
} else {
//「完了」ボタンを押したとき
//該当のタスクを検索
$task = Task::find($id);
//モデル->カラム名 = 値 で、データを割り当てる
$task->status = true; //true:完了、false:未完了
//データベースに保存
$task->save();
}
//リダイレクト
return redirect('/tasks');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
Task::find($id)->delete();
return redirect('/tasks');
}
}
index.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Todo</title>
@vite('resources/css/app.css')
</head>
<body class="flex flex-col min-h-[100vh]">
<header class="bg-slate-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6">
<div class="py-6">
<p class="text-white text-xl">Todoアプリ</p>
</div>
</div>
</header>
<main class="grow">
<div class="max-w-7xl mx-auto px-4 sm:px-6">
<div class="py-[100px]">
<p class="text-2xl font-bold text-center">今日は何する?</p>
<form action="/tasks" method="post" class="mt-10">
@csrf
<div class="flex flex-col items-center">
<label class="w-full max-w-3xl mx-auto">
<input
class="placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-4 pl-4 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm"
placeholder="洗濯物をする..." type="text" name="task_name" />
@error('task_name')
<div class="mt-3">
<p class="text-red-500">
{{ $message }}
</p>
</div>
@enderror
</label>
<button type="submit" class="mt-8 p-4 bg-slate-800 text-white w-full max-w-xs hover:bg-slate-900 transition-colors">
追加する
</button>
</div>
</form>
{{-- 追記 --}}
@if ($tasks->isNotEmpty())
<div class="max-w-7xl mx-auto mt-20">
<div class="inline-block min-w-full py-2 align-middle">
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<table class="min-w-full divide-y divide-gray-300">
<thead class="bg-gray-50">
<tr>
<th scope="col"
class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900">
タスク</th>
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
<span class="sr-only">Actions</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@foreach ($tasks as $item)
<tr>
<td class="px-3 py-4 text-sm text-gray-500">
<div>
{{ $item->name }}
</div>
</td>
<td class="p-0 text-right text-sm font-medium">
<div class="flex justify-end">
<div>
<form action="/tasks/{{ $item->id }}"
method="post"
class="inline-block text-gray-500 font-medium"
role="menuitem" tabindex="-1">
@csrf
@method('PUT')
<input type="hidden" name="status" value="{{ $item->status }}">
<button type="submit"
class="bg-emerald-700 py-4 w-20 text-white md:hover:bg-emerald-800 transition-colors">完了</button>
</form>
</div>
<div>
<a href="/tasks/{{ $item->id }}/edit/"
class="inline-block text-center py-4 w-20 underline underline-offset-2 text-sky-600 md:hover:bg-sky-100 transition-colors">編集</a>
</div>
<div>
<form onsubmit="return deleteTask();"
action="/tasks/{{ $item->id }}" method="post"
class="inline-block text-gray-500 font-medium"
role="menuitem" tabindex="-1">
@csrf
@method('DELETE')
<button type="submit"
class="py-4 w-20 md:hover:bg-slate-200 transition-colors">削除</button>
</form>
</div>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endif
{{-- 追記ここまで --}}
</div>
</div>
</main>
<footer class="bg-slate-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6">
<div class="py-4 text-center">
<p class="text-white text-sm">Todoアプリ</p>
</div>
</div>
</footer>
<script>
function deleteTask() {
if (confirm('本当に削除しますか?')) {
return true;
} else {
return false;
}
}
</script>
</body>
</html>
edit.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Todo</title>
@vite('resources/css/app.css')
</head>
<body class="flex flex-col min-h-[100vh]">
<header class="bg-slate-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6">
<div class="py-6">
<p class="text-white text-xl">Todoアプリ-編集画面</p>
</div>
</div>
</header>
<main class="grow grid place-items-center">
<div class="w-full mx-auto px-4 sm:px-6">
<div class="py-[100px]">
<form action="/tasks/{{ $task->id }}" method="post" class="mt-10">
@csrf
@method('PUT')
<div class="flex flex-col items-center">
<label class="w-full max-w-3xl mx-auto">
<input
class="placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-4 pl-4 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm"
type="text" name="task_name" value="{{ $task->name }}" />
@error('task_name')
<div class="mt-3">
<p class="text-red-500">
{{ $message }}
</p>
</div>
@enderror
</label>
<div class="mt-8 w-full flex items-center justify-center gap-10">
<a href="/tasks" class="block shrink-0 underline derline-offset-2">
戻る
</a>
<input type="hidden" name="status" value="9">
<button type="submit" class="p-4 bg-sky-800 text-white w-full max-w-xs hover:bg-sky-900 transition-colors">
編集する
</button>
</div>
</div>
</form>
</div>
</div>
</main>
<footer class="bg-slate-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6">
<div class="py-4 text-center">
<p class="text-white text-sm">Todoアプリ</p>
</div>
</div>
</footer>
</body>
</html>
web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TodoListController;
use App\Http\Controllers\TaskController;
Route::get('/', function () {
return view('welcome');
});
Route::get('/list', [TodoListController::class, 'index']);
Route::resource('tasks', TaskController::class);



WEBサーバー起動
npm run dev

php artisan serve

http://127.0.0.1:8000/tasks

最後に、VS CODEのPostgreSQL拡張機能のSQL実行機能について
右クリックメニューより、NewQueryを選択

select/insert/update/deleteのSQL実行が可能

-
前の記事
GitHub へ初回pushして、ファイル変更して再度push 2024.11.19
-
次の記事
windows WSL2(ubuntu) docker desktop環境に laravel11 でpostサンプルアプリ作り覚書き 2025.01.26