Pular para o conteúdo principal

Laravel

Laravel 13 + Eloquent + the official React starter kit (Inertia, Fortify login) + Sail (Docker, MySQL 8.4).

Requirements: PHP >= 8.3, Composer, Docker.

1. Create the project​

composer create-project laravel/react-starter-kit laravel --stability=dev
cd laravel

This is what laravel new laravel --react runs. --stability=dev matters: the starter kit is released from its main branch, and the tagged v1.0.1 is an old Laravel 12 kit.

The installer asks which authentication features to keep (email verification, registration, 2FA, passkeys, password confirmation). This build kept the defaults (all). The login screen, session auth, password reset, dashboard and profile pages are now in place — nothing to write for LOGIN.

If PHP runs in a container without Node, prefix the command with LARAVEL_INSTALLER_NO_NODE=1; the JavaScript packages are installed in step 5.

2. Add Docker (Sail) with MySQL​

php artisan sail:install --with=mysql

It writes compose.yaml and points .env to the mysql service. To run next to the other two projects, set the host ports in .env:

APP_URL=http://localhost:8081
APP_PORT=8081
FORWARD_DB_PORT=3307

3. Generate the Product skeleton​

php artisan make:model Product --migration --factory --controller --resource
php artisan make:request ProductRequest
php artisan make:test ProductTest

The files are generated empty: Laravel goes model -> migration -> table, so the columns, the rules, the controller bodies and the pages are written by hand.

4. Fill in the generated files​

Migration — database/migrations/*_create_products_table.php

Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name', 100);
$table->decimal('price', 10, 2);
$table->timestamps();
$table->softDeletes();
});

Model — app/Models/Product.php. Eloquent reads the columns from the table, so only the behaviour is declared:

#[Fillable(['name', 'price'])]
class Product extends Model
{
use HasFactory, SoftDeletes;
}

Validation — app/Http/Requests/ProductRequest.php (remove authorize(); the route is already behind auth):

public function rules(): array
{
return [
'name' => ['required', 'string', 'max:100'],
'price' => ['required', 'numeric', 'min:0', 'max:99999999.99'],
];
}

Controller — app/Http/Controllers/ProductController.php (the show method is not used and can be deleted):

public function index(): Response
{
return Inertia::render('products/index', [
'products' => Product::latest('id')->get(),
]);
}

public function create(): Response
{
return Inertia::render('products/form');
}

public function store(ProductRequest $request): RedirectResponse
{
Product::create($request->validated());

return to_route('products.index');
}

public function edit(Product $product): Response
{
return Inertia::render('products/form', ['product' => $product]);
}

public function update(ProductRequest $request, Product $product): RedirectResponse
{
$product->update($request->validated());

return to_route('products.index');
}

public function destroy(Product $product): RedirectResponse
{
$product->delete();

return to_route('products.index');
}

Route — routes/web.php, inside the existing auth group:

Route::resource('products', ProductController::class)->except('show');

Pages — there is no generator for these; write them by hand:

  • resources/js/types/product.ts — the Product type, exported from types/index.ts
  • resources/js/pages/products/index.tsx — the table with Edit / Delete buttons
  • resources/js/pages/products/form.tsx — one form used for both create and edit
  • resources/js/components/app-sidebar.tsx — add the Products entry to mainNavItems

The typed route helpers the pages import (@/routes/products, @/actions/App/Http/Controllers/ProductController) are generated by Wayfinder during the build.

Factory and seeder — database/factories/ProductFactory.php returns ['name' => fake()->words(3, true), 'price' => fake()->randomFloat(2, 1, 1000)], and database/seeders/DatabaseSeeder.php creates the admin user and five products:

User::factory()->create([
'name' => 'Administrator',
'email' => '[email protected]',
'password' => '!P4ssw0rdstr!',
]);

Product::factory(5)->create();

Tests — tests/Feature/ProductTest.php.

5. Run it​

./vendor/bin/sail up -d
./vendor/bin/sail npm install
./vendor/bin/sail npm run build
./vendor/bin/sail artisan migrate:fresh --seed
./vendor/bin/sail test

Open http://localhost:8081, log in with [email protected] / !P4ssw0rdstr! and use Products in the sidebar. For hot reload while editing pages, run ./vendor/bin/sail npm run dev.

Stop with ./vendor/bin/sail down (add -v to drop the database volume).

Screens and routes​

MethodPathResult
GET/POST/loginlogin screen (starter kit)
GET/productslist
GET/POST/products/create, /productsnew product
GET/PUT/products/{id}/edit, /products/{id}edit product
DELETE/products/{id}soft delete (sets deleted_at)

Guests are redirected to /login on every product route.

What was generated vs written​

Generated by commandsproject, login and all auth screens, Docker, the empty skeletons of 6 files, typed route helpers
Written by handcolumns in the migration, 2 lines of model behaviour, validation rules, 6 controller bodies, 1 route, 2 React pages + type + menu entry, seeder, tests

Laravel has no first-party generator that reads an existing table and writes the model, rules, controller and screens (the Gluo codegen direction). make:model produces empty skeletons; php artisan model:show Product inspects a model but writes nothing.