Laravel Tutorial · Laravel Basics · Lesson 5 of 27
Laravel Controllers
Move request handling out of route files into controller classes, use single-action and resource controllers, and inject dependencies.
Why controllers?
Closures in route files are fine for tiny apps, but real handlers grow. A controller groups related actions in a class:
php artisan make:controller PostController --resource
Route to a controller method with an array: Route::get('/posts', [PostController::class, 'index']).
Resource controllers follow a convention for CRUD: index, create, store, show, edit, update, destroy, and Route::resource('posts', PostController::class) registers all seven routes. For JSON APIs, Route::apiResource skips the HTML form routes (create, edit).
Dependency injection: type-hint services in the constructor or method parameters and Laravel's service container creates them for you.
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
class PriceFormatter
{
public function format(int $cents): string
{
return '$'.number_format($cents / 100, 2);
}
}
class ProductController
{
private array $products = [1 => ['name' => 'Mug', 'cents' => 1200], 2 => ['name' => 'Tea', 'cents' => 650]];
public function __construct(private PriceFormatter $prices) {} // injected by the container
public function index()
{
return array_map(fn ($p) => $p['name'].' '.$this->prices->format($p['cents']), array_values($this->products));
}
public function show(int $id)
{
abort_unless(isset($this->products[$id]), 404, 'Product not found');
return $this->products[$id];
}
public function store(Request $request)
{
return response()->json(['created' => $request->input('name')], 201);
}
}
Route::apiResource('products', ProductController::class)->only(['index', 'show', 'store']);
visit('GET', '/products');
visit('GET', '/products/2');
visit('GET', '/products/9');
visit('POST', '/products', ['name' => 'Kettle']);
foreach (app('router')->getRoutes() as $route) {
if (! str_starts_with($route->uri(), 'products')) continue; // skip the framework's own routes
echo str_pad(implode('|', $route->methods()), 10), ' ', str_pad($route->uri(), 18), ' ', $route->getName(), "\n";
}
Output
GET /products -> 200 ["Mug $12.00","Tea $6.50"]
GET /products/2 -> 200 {"name":"Tea","cents":650}
GET /products/9 -> 404 {
"message": "Product not found"
}
POST /products -> 201 {"created":"Kettle"}
GET|HEAD products products.index
POST products products.store
GET|HEAD products/{product} products.showRun this on your computer — see “Get Started” for setup.
A single-action controller has just an __invoke method: Route::post('/reports', GenerateReport::class).
Test yourself
Which method does Route::apiResource NOT register?
indexstorecreatedestroy
Show answer
Answer: create