Basic controllers — extending Controller, action methods
Concept
Controllers in Laravel are classes that group related HTTP request handling logic. They receive a Request, interact with services/models, and return a Response. Controllers are thin coordinators — they should not contain business logic.
Extending App\Http\Controllers\Controller: The base Controller class provides authorize(), validate(), dispatch(), and other helpers. You don't have to extend it, but you lose these conveniences.
Action methods: Methods that correspond to HTTP actions. The naming convention for resource controllers: index, create, store, show, edit, update, destroy. For non-resource controllers, any descriptive verb: search, export, activate.
Dependency injection in controllers: Laravel automatically injects type-hinted dependencies in controller constructors AND in action method parameters. public function index(Request $request, UserRepository $repo) — both are injected. Route parameters and injectable services are distinguished by whether they appear in the route definition.
Controller response return types: Return anything that Blade/JSON serialization understands: View, JsonResponse, RedirectResponse, Response, arrays (auto-JSON), Eloquent models/collections (auto-JSON), strings.
Single-responsibility principle: One controller per resource group. UserController handles users, OrderController handles orders. If a controller grows beyond 7-10 methods, consider splitting.
Code Example
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\View\View;
class UserController extends Controller
{
// Constructor injection — available to all methods
public function __construct(
private readonly UserService $userService,
) {}
// GET /users — list all users
public function index(Request $request): View|JsonResponse
{
$users = User::with('profile')
->when($request->search, fn($q, $s) => $q->where('name', 'like', "%$s%"))
->paginate(15);
if ($request->expectsJson()) {
return response()->json($users);
}
return view('users.index', compact('users'));
}
// GET /users/{user} — show one user
public function show(User $user): View
{
// $user auto-resolved via route model binding
return view('users.show', compact('user'));
}
// POST /users — create user
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:8',
]);
$user = $this->userService->create($validated);
return response()->json($user, 201);
}
// PUT /users/{user} — update user
public function update(Request $request, User $user): JsonResponse
{
$this->authorize('update', $user); // policy check
$validated = $request->validate([
'name' => 'sometimes|string|max:255',
'email' => 'sometimes|email|unique:users,email,' . $user->id,
]);
$user->update($validated);
return response()->json($user);
}
// DELETE /users/{user} — delete user
public function destroy(User $user): JsonResponse
{
$this->authorize('delete', $user);
$user->delete();
return response()->json(null, 204);
}
}