¿Qué es Laravel?
Laravel es un framework de PHP full-stack construido bajo una promesa sencilla: hacer que el desarrollo web sea agradable sin sacrificar la potencia. Sigue el patrón MVC, aunque la arquitectura importa menos que la experiencia de usarlo. El enrutamiento es expresivo, la capa de base de datos se lee casi como inglés y las piezas que normalmente tendrías que ensamblar usando una docena de librerías ya vienen integradas.
Fue creado por Taylor Otwell en 2011 y desde entonces se ha convertido en el framework de PHP más popular, con un ecosistema que va mucho más allá del framework en sí: Forge y Vapor para el despliegue, Nova para paneles de administración, Livewire para interfaces reactivas y Sanctum para la autenticación.
Si has escrito PHP a mano, usar Laravel se siente como si alguien finalmente hubiera organizado la caja de herramientas.
Enrutamiento y controladores
Cada solicitud entra a través de una ruta. Las rutas web se encuentran en routes/web.php y las rutas de la API en routes/api.php, y ambas utilizan la misma sintaxis expresiva.
<?php
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
Route::get('/posts', [PostController::class, 'index']);
Route::get('/posts/{post}', [PostController::class, 'show']);
Route::post('/posts', [PostController::class, 'store']);
Route::apiResource('posts', PostController::class);
Route::apiResource genera el conjunto convencional de rutas index, store, show, update y destroy en una sola línea. Posteriormente, un controlador contiene un método por acción, manteniendo cada manejador pequeño.
class PostController extends Controller
{
public function index()
{
return Post::latest()->paginate(20);
}
public function store(StorePostRequest $request)
{
$post = $request->user()->posts()->create($request->validated());
return response()->json($post, 201);
}
}
Route model binding
Laravel puede convertir automáticamente un parámetro de ruta en una instancia de un modelo. Nombra el parámetro según el modelo y añade el type-hint en el controlador, y Laravel recuperará el registro o devolverá un 404.
Route::get('/posts/{post}', [PostController::class, 'show']);
public function show(Post $post)
{
return $post->load('author');
}
Por defecto, se utiliza la clave primaria del modelo. Añade getRouteKeyName() para realizar el binding mediante un slug, lo que te permite obtener URLs limpias como /posts/hello-world sin necesidad de escribir código de búsqueda adicional.
Eloquent: el ORM en el que los desarrolladores se quedan
Eloquent es el ORM de active-record de Laravel. Un modelo se mapea a una tabla y las relaciones se declaran como métodos.
class Post extends Model
{
protected $fillable = ['title', 'slug', 'body'];
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function scopePublished($query)
{
return $query->whereNotNull('published_at');
}
}
Las relaciones incluyen hasMany, belongsTo, belongsToMany, hasManyThrough y variantes polimórficas. La regla práctica más importante es hacer eager load de las relaciones que sepas que vas a utilizar:
$posts = Post::with('author')->published()->latest()->get();
Sin with('author'), acceder a $post->author dentro de un bucle ejecuta una consulta por cada fila: el clásico problema N+1. El eager loading convierte esto en solo dos consultas, independientemente de cuántos posts recuperes.
Migraciones, factories y seeders
Los cambios de esquema son archivos PHP versionados, por lo que cada entorno se construye a partir del mismo historial.
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->timestamps();
});
Ejecútalos con php artisan migrate. Los factories describen cómo simular un modelo, y los seeders pueblan una base de datos con datos conocidos.
Post::factory()->count(50)->for(User::factory())->create();
Juntos, los factories y los seeders hacen que las pruebas sean realistas sin necesidad de escribir fixtures a mano.
Blade: plantillas sin sorpresas
Blade se compila a PHP puro y no interfiere en tu flujo de trabajo. La salida se escapa por defecto, y los layouts y componentes mantienen el marcado DRY.
{{-- resources/views/posts/show.blade.php --}}
@extends('layouts.app')
@section('content')
<article>
<h1>{{ $post->title }}</h1>
<p>By {{ $post->author->name }}</p>
{!! $post->body !!}
</article>
@endsection
{{ }} escapa el contenido, {!! !!} no lo hace. Utiliza la forma escapada en todas partes a menos que hayas saneado el contenido tú mismo, y prefiere componentes como <x-post-card :post="$post" /> para interfaces de usuario reutilizables.
Validación y form requests
La validación es una funcionalidad de primer nivel. Para cualquier caso que no sea puntual, mueve las reglas a un form request.
php artisan make:request StorePostRequest
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user() !== null;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string'],
'published_at' => ['nullable', 'date'],
];
}
}
Añade el type-hint del request en el controlador y Laravel validará antes de que se ejecute tu código, devolviendo automáticamente un 422 con los errores. Después, utiliza $request->validated() para que solo los campos conocidos lleguen al modelo.
Middleware y el ciclo de vida de la solicitud
El middleware filtra las solicitudes antes de que lleguen a una ruta. Las versiones recientes de Laravel los configuran en bootstrap/app.php.
->withMiddleware(function (Middleware $middleware) {
$middleware->api(prepend: [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
]);
$middleware->alias([
'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
]);
})
Una solicitud pasa a través del middleware global, luego por el middleware de ruta como auth y, finalmente, llega al controlador. Comprender este orden resuelve la mayoría de las dudas sobre “por qué falta este valor”.
El service container y las facades
El service container resuelve las dependencias por ti. Basta con añadir el type-hint de una clase en un constructor o controlador y Laravel la construirá, inyectando sus propias dependencias de forma recursiva.
class PostController extends Controller
{
public function __construct(private PostRepository $posts) {}
}
Las facades como Cache::get() y DB::table() ofrecen un atajo con apariencia estática hacia los servicios del container. Son convenientes en los controladores, pero la inyección por constructor mantiene las clases testeables y las dependencias explícitas. Prioriza la inyección en los servicios; utiliza facades donde realmente mejoren la legibilidad.
Artisan, colas y eventos
Artisan es la interfaz de línea de comandos que genera y ejecuta la aplicación.
php artisan make:model Post -mfc # model, migration, factory, controller
php artisan migrate
php artisan queue:work
php artisan schedule:run
Cualquier proceso lento —enviar correos, llamar a una API de terceros, generar un reporte— debe ir en un job en cola.
class SendPostNotification implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;
public function __construct(public Post $post) {}
public function handle(): void
{
// runs on a worker, not in the request
}
}
SendPostNotification::dispatch($post);
Los eventos y listeners siguen el mismo patrón, y los listeners pueden implementar ShouldQueue por sí mismos, permitiendo que la solicitud responda inmediatamente.
Autenticación con Sanctum
Para SPAs y APIs basadas en tokens, Sanctum es la opción predeterminada. Emite tokens de acceso personal y también soporta sesiones basadas en cookies para frontends de primera parte.
$token = $user->createToken('mobile')->plainTextToken;
Protege las rutas con el alias del middleware:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Passport sigue siendo la opción cuando necesitas un servidor OAuth2 completo con clientes, scopes y grants.
Pruebas con Pest y PHPUnit
Laravel incluye una configuración de pruebas que inicia el framework y puede reiniciar la base de datos en cada prueba utilizando el trait RefreshDatabase.
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('it creates a post', function () {
$user = User::factory()->create();
$this->actingAs($user)
->postJson('/api/posts', ['title' => 'Hello', 'body' => 'World'])
->assertCreated();
});
Pest añade una sintaxis basada en funciones más legible sobre PHPUnit. De cualquier manera, las pruebas HTTP ponen a prueba el enrutamiento, el middleware, la validación y la base de datos en conjunto, que es donde realmente se encuentran la mayoría de los bugs de Laravel.
Mejores prácticas
- Mantén los controladores ligeros; mueve la lógica de negocio a servicios y acciones.
- Usa form requests para la validación y persiste únicamente
$request->validated(). - Carga las relaciones de forma anticipada con
with()para evitar consultas N+1. - Versiona el esquema con migraciones y genera datos con factories.
- Prefiere la inyección por constructor sobre las facades en los servicios que quieras testear.
- Envía a colas cualquier proceso lento y mantén las solicitudes rápidas.
- Escribe feature tests que ejerciten la capa HTTP y una base de datos real.
Errores comunes
- Llamar a
Post::create($request->all())y exponer columnas a la asignación masiva (mass assignment). - Cargar relaciones mediante lazy loading dentro de un bucle y saturar la base de datos.
- Colocar consultas, validaciones y reglas de negocio en un único método gigante del controlador.
- Olvidar
$fillabley preguntarse por qué se omiten atributos silenciosamente. - Ejecutar tareas pesadas de forma síncrona en lugar de despachar un job.
- Editar una migración antigua después de haber sido ejecutada en producción.
- Asumir que
{{ }}y{!! !!}son intercambiables.
Próximos pasos
Laravel enseña una forma productiva y estructurada de construir aplicaciones web, y gran parte de su base proviene de los componentes de Symfony, por lo que esa guía es el complemento natural. Si vienes del mundo de JavaScript, compara la mentalidad con Express. Para diseñar la API que exponen tus rutas, lee sobre REST y luego sobre OpenAPI para la documentación.