Download Laravel App
https://laravel.com/docs/12.x/installation
Connecting our Database
open .env file root directory.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root
Database Migration
php artisan migrate
myapp>php artisan migrate
Migration table created successfully.
check database table
Create tables Model php artisan make:model Post -m myapp>php artisan make:model Post -m Open new products migrations yourproject/database/migrations laravelproject\database\migrations\_create_posts_table.php
//laravelproject\database\migrations\_create_posts_table.php
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->string('picture')->nullable(); // Add picture column (nullable)
$table->timestamps();
});
}
run myapp>php artisan migrate
update Product Model
app/models/Product.php
//app/models/Product.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = ['title', 'content', 'picture'];
}
php artisan make:controller PostController
change it with the following codes: app\Http\Controllers\PostController.php
//app\Http\Controllers\PostController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Post;
use Inertia\Inertia;
use Inertia\Response;
class PostController extends Controller
{
public function index(): Response
{
return Inertia::render('Posts', [
'posts' => Post::all(),
]);
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'picture' => 'nullable|image|max:2048', // Validate that picture is an image
]);
$data = $request->only(['title', 'content']);
if ($request->hasFile('picture')) {
$file = $request->file('picture');
$filename = time() . '_' . $file->getClientOriginalName();
// Store the file in the "public/uploads" directory
$path = $file->storeAs('uploads', $filename, 'public');
$data['picture'] = '/storage/' . $path; //php artisan storage:link
}
Post::create($data);
return redirect()->route('posts.index')->with('success', 'Post created successfully.');
}
public function update(Request $request, Post $post)
{
$request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'picture' => 'nullable|image|max:2048',
]);
$data = $request->only(['title', 'content']);
if ($request->hasFile('picture')) {
$file = $request->file('picture');
$filename = time() . '_' . $file->getClientOriginalName();
$path = $file->storeAs('uploads', $filename, 'public');
$data['picture'] = '/storage/' . $path;
}
$post->update($data);
return redirect()->route('posts.index')->with('success', 'Post updated successfully.');
}
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index')->with('success', 'Post deleted successfully.');
}
}
php artisan make:controller UsersController
change it with the following codes: app\Http\Controllers\UsersController.php
//app\Http\Controllers\UsersController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use Inertia\Response;
use Inertia\Inertia;
class UsersController extends Controller
{
public function index(): Response
{
$users = User::select('id', 'name', 'email', 'created_at')->latest()->paginate(10);
return Inertia::render('users', [
'users' => $users,
]);
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email',
]);
User::create($request->only('name', 'email'));
return redirect()->route('users.index');
}
public function update(Request $request, $id)
{
$user = User::findOrFail($id);
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email,' . $id,
]);
$user->update($request->only('name', 'email'));
return redirect()->route('users.index');
}
public function destroy($id)
{
User::findOrFail($id)->delete();
return redirect()->route('users.index');
}
}
Frontend with React and InertiaJS Add sidebar menu product mainNavItems from reactjs\rescourses\js\components\app-sidebar.tsx
File: pages\Posts.tsx
reactjs\resources\js\pages\Posts.tsx
//reactjs\resources\js\pages\Posts.tsx
import AppLayout from '@/layouts/app-layout';
import { Head, router, usePage } from "@inertiajs/react";
import { useState } from "react";
import PostFormModal from "@/components/PostFormModal";
export default function Posts() {
const { posts } = usePage<{ posts: { id: number; title: string; content: string; picture?: string }[] }>().props;
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedPost, setSelectedPost] = useState(null);
const openModal = (post = null) => {
setSelectedPost(post);
setIsModalOpen(true);
};
const handleDelete = (id: number) => {
router.delete(`/admin/posts/${id}`, {
onSuccess: () => {
router.reload();
},
onError: () => {
console.error("Failed to delete post.");
},
});
};
return (
<AppLayout>
<Head title="Posts" />
<div className="container mx-auto p-4">
<div className="flex justify-between items-center mb-4">
<h1 className="text-2xl font-bold">Posts</h1>
<button onClick={() => openModal()} className="bg-green-600 text-white rounded px-3 py-1 text-sm hover:bg-green-700 transition">
Add Post
</button>
</div>
<div className="overflow-x-auto">
<table className="min-w-full bg-white shadow rounded-lg">
<thead>
<tr className="bg-gray-200 text-black">
<th className="py-2 px-4 text-left border-b">ID</th>
<th className="py-2 px-4 text-left border-b">Photo</th>
<th className="py-2 px-4 text-left border-b">Title</th>
<th className="py-2 px-4 text-left border-b">Content</th>
<th className="py-2 px-4 text-left border-b">Actions</th>
</tr>
</thead>
<tbody>
{posts.length ? (
posts.map((post) => (
<tr key={post.id} className="hover:bg-gray-50 text-black">
<td className="py-2 px-4 border-b">{post.id}</td>
<td className="py-2 px-4 border-b">
{post.picture ? <img src={post.picture} alt="Post" className="w-16 h-16 object-cover rounded-full" /> : "No Picture"}
</td>
<td className="py-2 px-4 border-b">{post.title}</td>
<td className="py-2 px-4 border-b">{post.content}</td>
<td className="py-2 px-4 border-b">
<button onClick={() => openModal(post)} className="bg-blue-500 text-sm text-white px-3 py-2 rounded">Edit</button>
<button onClick={() => handleDelete(post.id)} className="bg-red-500 text-sm text-white px-3 py-2 rounded">Delete</button>
</td>
</tr>
))
) : (
<tr><td colSpan={4} className="text-center p-4 text-gray-600">No posts found.</td></tr>
)}
</tbody>
</table>
</div>
</div>
<PostFormModal isOpen={isModalOpen} closeModal={() => setIsModalOpen(false)} post={selectedPost} />
</AppLayout>
);
}
File: pages/users.tsx
reactjs\resources\js\pages\users.tsx
//reactjs\resources\js\pages\users.tsx
import { useState } from "react";
import AppLayout from "@/layouts/app-layout";
import { Head, router, usePage } from "@inertiajs/react";
import UserFormModal from "@/components/UserFormModal";
export default function Users() {
const { users } = usePage<{ users: { data: { id: number; name: string; email: string; created_at: string }[] } }>().props;
//console.log(users.data);
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedUser, setSelectedUser] = useState(null);
const openModal = (user = null) => {
setSelectedUser(user);
setIsModalOpen(true);
};
const handleDelete = (id: number) => {
if (confirm("Are you sure you want to delete this user?")) {
router.delete(`/admin/users/${id}`);
}
};
return (
<AppLayout>
<Head title="Users" />
<div className="container mx-auto p-4">
<div className="flex justify-between items-center mb-4">
<h1 className="text-2xl font-bold">Users</h1>
<button
onClick={() => openModal()}
className="mb-4 sm:w-auto bg-green-600 text-white rounded-lg px-4 py-2 hover:bg-green-700 transition">
Add User
</button>
</div>
<div className="overflow-x-auto">
<table className="min-w-full bg-white shadow rounded-lg">
<thead>
<tr className="bg-gray-200 text-black">
<th className="py-2 px-4 text-left border-b">ID</th>
<th className="py-2 px-4 text-left border-b">Name</th>
<th className="py-2 px-4 text-left border-b">Email</th>
<th className="py-2 px-4 text-left border-b">Created At</th>
<th className="py-2 px-4 text-left border-b">Actions</th>
</tr>
</thead>
<tbody>
{users.data.length ? (
users.data.map(({ id, name, email, created_at }) => (
<tr key={id} className="hover:bg-gray-50 text-black">
{[id, name, email, new Date(created_at).toLocaleDateString()].map((value, i) => (
<td key={i} className="border p-3 text-gray-700 text-xs sm:text-sm">
{value}
</td>
))}
<td className="py-2 px-4 border-b">
<button
onClick={() => openModal({ id, name, email })}
className="w-full sm:w-auto bg-blue-600 text-white rounded-lg px-4 py-2 hover:bg-blue-700"
>
Edit
</button>
<button
onClick={() => handleDelete(id)}
className="w-full sm:w-auto bg-red-600 text-white rounded-lg px-4 py-2 hover:bg-red-700 transition transition ml-2"
>
Delete
</button>
</td>
</tr>
))
) : (
<tr>
<td colSpan={5} className="text-center p-4 text-gray-600">
No users found.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
<UserFormModal isOpen={isModalOpen} closeModal={() => setIsModalOpen(false)} user={selectedUser} />
</AppLayout>
);
}
File: components/PostFormModal.tsx
reactjs\resources\js\components\PostFormModal.tsx
//reactjs\resources\js\components\PostFormModal.tsx
import { Input } from "@headlessui/react";
import { useState, useEffect } from "react";
import { router, usePage } from "@inertiajs/react";
import InputError from '@/components/input-error';
interface Post {
id?: number;
title: string;
content: string;
picture?: string;
}
interface Props {
isOpen: boolean;
closeModal: () => void;
post?: Post | null;
}
export default function PostFormModal({ isOpen, closeModal, post }: Props) {
const [formData, setFormData] = useState<Post>({ title: "", content: "", picture: "" });
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [preview, setPreview] = useState<string>("");
const { errors } = usePage().props;
useEffect(() => {
if (post) {
setFormData({ title: post.title, content: post.content, picture: post.picture || "" });
setPreview(post.picture || "");
setSelectedFile(null);
} else {
setFormData({ title: "", content: "", picture: "" });
setPreview("");
setSelectedFile(null);
}
}, [post]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
setSelectedFile(file);
setPreview(URL.createObjectURL(file));
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const data = new FormData();
data.append("title", formData.title);
data.append("content", formData.content);
if (selectedFile) {
data.append("picture", selectedFile);
}
if (post?.id) {
data.append("_method", "PUT");
router.post(`/admin/posts/${post.id}`, data, {
onSuccess: () => {
closeModal();
router.reload();
},
onError: (errors) => {
console.error(errors.message || "Failed to submit post.");
},
});
} else {
router.post("/admin/posts", data, {
onSuccess: () => {
closeModal();
router.reload();
},
onError: (errors) => {
console.error(errors.message || "Failed to submit post.");
},
});
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
<div className="bg-white p-6 rounded-lg shadow-lg w-full max-w-xl text-black">
<h2 className="text-lg font-semibold mb-4">Add Post</h2>
<form onSubmit={handleSubmit} encType="multipart/form-data">
<div className="mb-3">
<label className="block text-sm font-medium">Title</label>
<Input
type="text"
name="title"
value={formData.title}
onChange={handleChange}
className="w-full border rounded p-2"
/>
<InputError message={errors.title} />
</div>
<div className="mb-3">
<label className="block text-sm font-medium">Content</label>
<textarea
name="content"
value={formData.content}
onChange={handleChange}
className="w-full border rounded p-2"
></textarea>
<InputError message={errors.content} />
</div>
<div className="mb-3">
<label className="block text-sm font-medium">Picture (optional)</label>
<Input
type="file"
name="picture"
onChange={handleFileChange}
className="w-full"
accept="image/*"
/>
<InputError message={errors.picture} />
</div>
{preview && (
<div className="mb-3">
<p className="text-sm mb-1">Image Preview:</p>
<img src={preview} alt="Preview" className="w-32 h-32 object-cover rounded" />
</div>
)}
<div className="flex justify-end gap-2">
<button type="button" onClick={closeModal} className="px-4 py-2 bg-gray-500 text-white rounded">Cancel</button>
<button type="submit" className="px-4 py-2 bg-blue-600 text-white rounded">{post ? "Update" : "Create"}</button>
</div>
</form>
</div>
</div>
);
}
File: components/UserFormModal.tsx
reactjs\resources\js\components\PostFormModal.tsx
//reactjs\resources\js\components\UserFormModal.tsx
import { Input } from "@headlessui/react";
import { useState, useEffect } from "react";
import { router, usePage } from "@inertiajs/react";
import InputError from "./input-error";
interface User {
id?: number;
name: string;
email: string;
}
interface Props {
isOpen: boolean;
closeModal: () => void;
user?: User | null;
}
export default function UserFormModal({ isOpen, closeModal, user }: Props) {
const [formData, setFormData] = useState<User>({ name: "", email: "" });
const { errors } = usePage().props;
useEffect(() => {
if (user) {
setFormData({ name: user.name, email: user.email });
} else {
setFormData({ name: "", email: "" });
}
}, [user]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (user?.id) {
router.put(`/admin/users/${user.id}`, formData, { onSuccess: closeModal });
} else {
router.post("/admin/users", formData, { onSuccess: closeModal });
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
<div className="bg-white p-6 rounded-lg shadow-lg w-full max-w-xl text-black">
<h2 className="text-lg font-semibold mb-4">Add User</h2>
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label className="block text-sm font-medium">Name</label>
<Input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
className="w-full border rounded p-2"
/>
<InputError message={errors.name} />
</div>
<div className="mb-3">
<label className="block text-sm font-medium">Email</label>
<Input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
className="w-full border rounded p-2"
/>
<InputError message={errors.email} />
</div>
<div className="flex justify-end gap-2">
<button
type="button"
onClick={closeModal}
className="px-4 py-2 bg-gray-500 text-white rounded"
>
Cancel
</button>
<button type="submit" className="px-4 py-2 bg-blue-600 text-white rounded">
{user ? "Update" : "Create"}
</button>
</div>
</form>
</div>
</div>
);
}
Routes routes/web.php
//routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\PostController;
use App\Http\Controllers\UsersController;
Route::get('/', function () {
return Inertia::render('welcome');
})->name('home');
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('dashboard', function () {
return Inertia::render('dashboard');
})->name('dashboard');
Route::resource('admin/posts', PostController::class);
Route::get('/admin/users', [UsersController::class, 'index'])->name('users.index');
Route::post('/admin/users', [UsersController::class, 'store'])->name('users.store');
Route::put('/admin/users/{id}', [UsersController::class, 'update'])->name('users.update');
Route::delete('/admin/users/{id}', [UsersController::class, 'destroy'])->name('users.destroy');
});
require __DIR__.'/settings.php';
require __DIR__.'/auth.php';
Run php artisan serve and npm run dev myapp>composer run dev Starting Laravel development server: http://127.0.0.1:8000
