Tema
Rutas
Las rutas son el "directorio" de tu aplicación: dicen qué código se ejecuta cuando alguien visita una URL con un método HTTP. En esta lección aprenderás a definir rutas con parámetros, darles nombre, agruparlas, usar rutas de recurso y aprovechar el route model binding.
Concepto
Imagina el conmutador de una empresa: cuando llamas y marcas la extensión 105, te comunica con Ventas; la 200, con Soporte. El router de Laravel hace lo mismo: recibe GET /productos y "te comunica" con la función o controlador que corresponde.
Una ruta tiene tres partes:
php
Route::get('/productos', [ProductoController::class, 'index']);
// ↑ ↑ ↑
// método URL qué se ejecuta (acción)Las rutas de páginas web van en routes/web.php. Todas tienen sesión, cookies y protección CSRF activadas automáticamente.
Métodos HTTP
php
<?php
use Illuminate\Support\Facades\Route;
Route::get('/productos', fn () => 'Listar productos'); // Leer
Route::post('/productos', fn () => 'Guardar producto'); // Crear
Route::put('/productos/{id}', fn ($id) => "Reemplazar $id"); // Actualizar todo
Route::patch('/productos/{id}', fn ($id) => "Actualizar $id"); // Actualizar parte
Route::delete('/productos/{id}', fn ($id) => "Eliminar $id"); // Borrar
// Varios métodos a la vez
Route::match(['get', 'post'], '/contacto', fn () => 'Contacto');
// Cualquier método (úsalo poco)
Route::any('/webhook', fn () => 'ok');Formularios y PUT/DELETE
Los formularios HTML solo envían GET y POST. Para PUT, PATCH o DELETE, Laravel usa un campo oculto _method que generas con la directiva @method('DELETE') en Blade. Lo verás en la lección de Blade.
Rutas que solo muestran una vista o redirigen
php
// Muestra resources/views/nosotros.blade.php sin necesidad de controlador
Route::view('/nosotros', 'nosotros');
// Pasando datos a la vista
Route::view('/aviso-privacidad', 'legal.privacidad', ['empresa' => 'Taquería El Güero']);
// Redirecciones (301 = permanente, útil para SEO)
Route::redirect('/inicio', '/');
Route::permanentRedirect('/blog-viejo', '/blog');Parámetros
php
// Parámetro obligatorio
Route::get('/usuarios/{id}', function (string $id) {
return "Usuario número $id";
});
// Varios parámetros (se inyectan en el mismo orden)
Route::get('/posts/{post}/comentarios/{comentario}', function (string $post, string $comentario) {
return "Post $post, comentario $comentario";
});
// Parámetro opcional: lleva "?" y un valor por defecto
Route::get('/saludo/{nombre?}', function (?string $nombre = 'invitado') {
return "Hola, $nombre";
});Restringir parámetros con expresiones regulares
php
// Solo números
Route::get('/usuarios/{id}', fn ($id) => "Usuario $id")
->whereNumber('id');
// Solo letras
Route::get('/categorias/{slug}', fn ($slug) => $slug)
->whereAlpha('slug');
// Expresión regular personalizada: letras minúsculas, números y guiones
Route::get('/blog/{slug}', fn ($slug) => $slug)
->where('slug', '[a-z0-9-]+');
// Valores permitidos
Route::get('/reportes/{periodo}', fn ($periodo) => $periodo)
->whereIn('periodo', ['dia', 'semana', 'mes']);Si la URL no cumple la restricción, Laravel responde 404 automáticamente.
Rutas con nombre
Darle nombre a una ruta te permite generar su URL sin escribirla a mano. Si mañana cambias /contacto por /contactanos, todos los enlaces se actualizan solos.
php
Route::get('/contacto', fn () => view('contacto'))->name('contacto');
Route::get('/productos/{id}', fn ($id) => $id)->name('productos.show');Generar URLs y redirecciones:
php
// En PHP (controladores, rutas...)
$url = route('contacto'); // http://mi-app.test/contacto
$url = route('productos.show', ['id' => 5]); // http://mi-app.test/productos/5
$url = route('productos.show', 5); // forma corta
return redirect()->route('contacto'); // redirigir a una ruta con nombre
return to_route('productos.show', 5); // atajo equivalenteEn una vista Blade:
blade
<a href="{{ route('contacto') }}">Contáctanos</a>
<a href="{{ route('productos.show', $producto->id) }}">Ver producto</a>Buena práctica
Nombra todas tus rutas y usa siempre route('nombre') en lugar de escribir URLs a mano. Usa la convención recurso.accion: productos.index, productos.create, productos.store...
Grupos de rutas
Cuando varias rutas comparten prefijo, nombre o middleware, agrúpalas:
php
<?php
use App\Http\Controllers\Admin\DashboardController;
use App\Http\Controllers\Admin\UsuarioController;
use Illuminate\Support\Facades\Route;
Route::middleware('auth') // todas requieren iniciar sesión
->prefix('admin') // todas empiezan por /admin
->name('admin.') // todos los nombres empiezan por admin.
->group(function () {
// GET /admin → nombre: admin.dashboard
Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
// GET /admin/usuarios → nombre: admin.usuarios
Route::get('/usuarios', [UsuarioController::class, 'index'])->name('usuarios');
});También puedes agrupar por controlador para no repetirlo:
php
use App\Http\Controllers\PedidoController;
Route::controller(PedidoController::class)->group(function () {
Route::get('/pedidos', 'index')->name('pedidos.index');
Route::get('/pedidos/{id}', 'show')->name('pedidos.show');
Route::post('/pedidos', 'store')->name('pedidos.store');
});Rutas de recurso (CRUD en una línea)
Para un CRUD típico necesitas 7 rutas. Laravel las crea todas con una sola línea:
php
use App\Http\Controllers\ProductoController;
Route::resource('productos', ProductoController::class);Esto genera:
| Método | URL | Acción | Nombre | Para qué |
|---|---|---|---|---|
| GET | /productos | index | productos.index | Listar |
| GET | /productos/create | create | productos.create | Formulario de alta |
| POST | /productos | store | productos.store | Guardar nuevo |
| GET | /productos/{producto} | show | productos.show | Ver uno |
| GET | /productos/{producto}/edit | edit | productos.edit | Formulario de edición |
| PUT/PATCH | /productos/{producto} | update | productos.update | Guardar cambios |
| DELETE | /productos/{producto} | destroy | productos.destroy | Eliminar |
Puedes limitar o excluir acciones:
php
// Solo algunas
Route::resource('fotos', FotoController::class)->only(['index', 'show']);
// Todas menos algunas
Route::resource('categorias', CategoriaController::class)->except(['show']);
// Varias a la vez
Route::resources([
'clientes' => ClienteController::class,
'facturas' => FacturaController::class,
]);Route model binding
Si el nombre del parámetro coincide con una variable tipada con un modelo, Laravel busca el registro por ti y responde 404 si no existe:
php
use App\Models\Producto;
// {producto} → Laravel ejecuta Producto::findOrFail($valor)
Route::get('/productos/{producto}', function (Producto $producto) {
return $producto->nombre;
});
// Buscar por otra columna (ej. slug) en lugar del id
Route::get('/blog/{post:slug}', function (App\Models\Post $post) {
return $post->titulo; // /blog/mi-primer-post
});Ruta de respaldo (404 personalizado)
php
// Debe ir AL FINAL del archivo
Route::fallback(function () {
return response()->view('errores.no-encontrado', [], 404);
});Aunque normalmente basta con crear resources/views/errors/404.blade.php: Laravel la usa automáticamente.
Ejemplo completo: routes/web.php de un sitio de negocio
php
<?php
use App\Http\Controllers\ContactoController;
use App\Http\Controllers\ProductoController;
use App\Models\Producto;
use Illuminate\Support\Facades\Route;
// Páginas estáticas
Route::view('/', 'inicio')->name('inicio');
Route::view('/nosotros', 'nosotros')->name('nosotros');
// Formulario de contacto: GET muestra, POST procesa
Route::get('/contacto', [ContactoController::class, 'create'])->name('contacto');
Route::post('/contacto', [ContactoController::class, 'store'])->name('contacto.enviar');
// Catálogo público: solo listar y ver
Route::get('/catalogo', function () {
$productos = Producto::where('activo', true)->orderBy('nombre')->get();
return view('catalogo.index', compact('productos'));
})->name('catalogo');
Route::get('/catalogo/{producto:slug}', function (Producto $producto) {
return view('catalogo.show', compact('producto'));
})->name('catalogo.show');
// Panel administrativo protegido
Route::middleware('auth')->prefix('admin')->name('admin.')->group(function () {
Route::resource('productos', ProductoController::class);
});Error común: el orden importa
Laravel revisa las rutas de arriba hacia abajo y usa la primera que coincide. Esto falla:
php
Route::get('/productos/{id}', ...); // atrapa también /productos/nuevo
Route::get('/productos/nuevo', ...); // nunca se alcanzaPon las rutas fijas antes que las de parámetros, o restringe el parámetro con whereNumber('id').
Error común: 419 Page Expired
Si haces un POST desde un formulario sin el token CSRF, Laravel responde 419. Agrega @csrf dentro de tu formulario Blade. Más detalles en CSRF.
Depuración
php artisan route:list muestra todas las rutas con su método, URL, nombre y acción. Filtra con php artisan route:list --name=admin o --path=productos.
Ejercicios
- Crea una ruta
/edad/{anios}que solo acepte números y responda "Tienes X años". - Crea un grupo con prefijo
cuentay nombrecuenta.con tres rutas:/perfil,/pedidosy/direcciones, cada una con su nombre. - Registra un recurso
clientessin la acciónshowy verifica conroute:listqué rutas se generaron. - Crea una ruta
/buscar/{termino?}donde el término sea opcional; si no viene, responde "Escribe algo para buscar".
Ver solución
- Edad:
php
Route::get('/edad/{anios}', function (int $anios) {
return "Tienes $anios años";
})->whereNumber('anios')->name('edad');- Grupo de cuenta:
php
Route::prefix('cuenta')->name('cuenta.')->group(function () {
Route::view('/perfil', 'cuenta.perfil')->name('perfil'); // cuenta.perfil
Route::view('/pedidos', 'cuenta.pedidos')->name('pedidos'); // cuenta.pedidos
Route::view('/direcciones', 'cuenta.direcciones')->name('direcciones');
});- Recurso sin
show:
php
use App\Http\Controllers\ClienteController;
Route::resource('clientes', ClienteController::class)->except(['show']);powershell
php artisan make:controller ClienteController --resource
php artisan route:list --path=clientesSe generan 6 rutas: index, create, store, edit, update y destroy.
- Búsqueda opcional:
php
Route::get('/buscar/{termino?}', function (?string $termino = null) {
if ($termino === null) {
return 'Escribe algo para buscar';
}
return 'Buscando: ' . e($termino); // e() escapa HTML para evitar XSS
})->name('buscar');Resumen
- Las rutas web van en
routes/web.phpy unen un método HTTP + URL con una acción. - Usa parámetros con
{nombre}, opcionales con{nombre?}y restringe conwhereNumber,where,whereIn. - Nombra tus rutas con
->name()y genera URLs conroute(). - Agrupa con
prefix,name,middlewareycontroller. Route::resource()crea las 7 rutas de un CRUD.- El route model binding busca el modelo automáticamente y lanza 404 si no existe.
- El orden importa: rutas fijas antes que rutas con parámetros.