Skip to content

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 equivalente

En 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étodoURLAcciónNombrePara qué
GET/productosindexproductos.indexListar
GET/productos/createcreateproductos.createFormulario de alta
POST/productosstoreproductos.storeGuardar nuevo
GET/productos/{producto}showproductos.showVer uno
GET/productos/{producto}/editeditproductos.editFormulario de edición
PUT/PATCH/productos/{producto}updateproductos.updateGuardar cambios
DELETE/productos/{producto}destroyproductos.destroyEliminar

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 alcanza

Pon 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 ​

  1. Crea una ruta /edad/{anios} que solo acepte números y responda "Tienes X años".
  2. Crea un grupo con prefijo cuenta y nombre cuenta. con tres rutas: /perfil, /pedidos y /direcciones, cada una con su nombre.
  3. Registra un recurso clientes sin la acción show y verifica con route:list qué rutas se generaron.
  4. Crea una ruta /buscar/{termino?} donde el término sea opcional; si no viene, responde "Escribe algo para buscar".
Ver solución
  1. Edad:
php
Route::get('/edad/{anios}', function (int $anios) {
    return "Tienes $anios años";
})->whereNumber('anios')->name('edad');
  1. 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');
});
  1. 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=clientes

Se generan 6 rutas: index, create, store, edit, update y destroy.

  1. 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.php y unen un método HTTP + URL con una acción.
  • Usa parámetros con {nombre}, opcionales con {nombre?} y restringe con whereNumber, where, whereIn.
  • Nombra tus rutas con ->name() y genera URLs con route().
  • Agrupa con prefix, name, middleware y controller.
  • 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.

Academia Web — aprende, copia y construye.