Laravel 11: Construir una Aplicacion Completa desde Cero
Guia completa para construir una aplicacion con Laravel 11: autenticacion, API REST, Eloquent ORM y despliegue a produccion. Tutorial practico para desarrolladores principiantes e intermedios.

Laravel 11 redefine el desarrollo moderno en PHP con una arquitectura simplificada y un rendimiento optimizado. Esta version mayor elimina archivos de configuracion innecesarios, presenta una estructura mas liviana y mejora significativamente la experiencia del desarrollador. Esta guia recorre la construccion de una aplicacion completa de gestion de tareas, desde la instalacion hasta el despliegue.
Laravel 11 adopta una estructura minimalista: sin archivos Kernel, configuracion consolidada en bootstrap/app.php y un directorio app/ mas limpio. El framework requiere PHP 8.2 como minimo e integra SQLite de forma nativa para un inicio rapido.
Instalacion y Configuracion del Proyecto
Laravel 11 se instala mediante Composer o el instalador de Laravel. La nueva estructura del proyecto es mas liviana, con menos archivos de configuracion que gestionar desde el inicio.
# terminal
# Install the Laravel installer (recommended)
composer global require laravel/installer
# Create a new Laravel 11 project
laravel new task-manager
# Or directly with Composer
composer create-project laravel/laravel task-manager
# Navigate to the project
cd task-managerEl instalador ofrece varias opciones interactivas: eleccion de starter kit (Breeze, Jetstream), framework de testing (Pest, PHPUnit) y base de datos.
# terminal
# Start the development server
php artisan serve
# Server starts at http://localhost:8000Configuracion de la Base de Datos
Laravel 11 utiliza SQLite por defecto, ideal para desarrollo. Para aplicaciones en produccion, la configuracion de MySQL o PostgreSQL se realiza en el archivo .env.
# .env
# MySQL configuration
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=task_manager
DB_USERNAME=root
DB_PASSWORD=secret
# PostgreSQL configuration (alternative)
# DB_CONNECTION=pgsql
# DB_HOST=127.0.0.1
# DB_PORT=5432
# DB_DATABASE=task_manager
# DB_USERNAME=postgres
# DB_PASSWORD=secretLa verificacion de la conexion a la base de datos se hace mediante un comando Artisan.
# terminal
# Verify database connection
php artisan db:show
# Run default migrations
php artisan migrateCreacion de Modelos y Migraciones
Eloquent ORM simplifica la manipulacion de datos con modelos expresivos. El comando make:model genera el modelo, la migracion, el controlador y el factory de una sola vez.
# terminal
# Generate Task model with migration, factory, seeder, and controller
php artisan make:model Task -mfsc
# -m : migration
# -f : factory
# -s : seeder
# -c : controllerEste comando crea cuatro archivos esenciales para la gestion completa de tareas.
<?php
// database/migrations/2026_01_14_000000_create_tasks_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
// Migration to create the tasks table
return new class extends Migration
{
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
// Auto-incrementing primary key
$table->id();
// Relationship with the owner user
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
// Task title (required)
$table->string('title');
// Detailed description (optional)
$table->text('description')->nullable();
// Status: pending, in_progress, completed
$table->string('status')->default('pending');
// Priority: low, medium, high
$table->string('priority')->default('medium');
// Due date (optional)
$table->date('due_date')->nullable();
// Completion marker
$table->timestamp('completed_at')->nullable();
// Automatic timestamps (created_at, updated_at)
$table->timestamps();
// Soft deletes for trash functionality
$table->softDeletes();
// Indexes to optimize frequent queries
$table->index(['user_id', 'status']);
$table->index('due_date');
});
}
public function down(): void
{
Schema::dropIfExists('tasks');
}
};La migracion define la estructura de la tabla con relaciones, indices y soft delete para prevenir la perdida accidental de datos.
<?php
// app/Models/Task.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Builder;
class Task extends Model
{
// Traits for advanced functionality
use HasFactory, SoftDeletes;
// Mass-assignable attributes
protected $fillable = [
'user_id',
'title',
'description',
'status',
'priority',
'due_date',
'completed_at',
];
// Automatic attribute casting
protected function casts(): array
{
return [
'due_date' => 'date',
'completed_at' => 'datetime',
];
}
// Constants for valid statuses
public const STATUS_PENDING = 'pending';
public const STATUS_IN_PROGRESS = 'in_progress';
public const STATUS_COMPLETED = 'completed';
// Constants for priorities
public const PRIORITY_LOW = 'low';
public const PRIORITY_MEDIUM = 'medium';
public const PRIORITY_HIGH = 'high';
// Relationship: a task belongs to a user
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
// Scope: pending tasks
public function scopePending(Builder $query): Builder
{
return $query->where('status', self::STATUS_PENDING);
}
// Scope: in-progress tasks
public function scopeInProgress(Builder $query): Builder
{
return $query->where('status', self::STATUS_IN_PROGRESS);
}
// Scope: completed tasks
public function scopeCompleted(Builder $query): Builder
{
return $query->where('status', self::STATUS_COMPLETED);
}
// Scope: overdue tasks
public function scopeOverdue(Builder $query): Builder
{
return $query->where('due_date', '<', now())
->whereNull('completed_at');
}
// Scope: tasks for a specific user
public function scopeForUser(Builder $query, int $userId): Builder
{
return $query->where('user_id', $userId);
}
// Accessor: check if task is overdue
public function getIsOverdueAttribute(): bool
{
return $this->due_date
&& $this->due_date->isPast()
&& !$this->completed_at;
}
// Method: mark as completed
public function markAsCompleted(): void
{
$this->update([
'status' => self::STATUS_COMPLETED,
'completed_at' => now(),
]);
}
}El modelo Task utiliza Eloquent scopes para consultas legibles y reutilizables. Las constantes centralizan los valores validos para estados y prioridades.
El trait SoftDeletes agrega una columna deleted_at. Las tareas eliminadas no desaparecen de la base de datos, sino que quedan marcadas como eliminadas. Se utiliza withTrashed() para incluirlas en las consultas.
Configuracion de Autenticacion con Breeze
Laravel Breeze proporciona autenticacion completa con vistas Blade o una API. La instalacion toma unos minutos y genera todo lo necesario: rutas, controladores, vistas y tests.
# terminal
# Install Laravel Breeze
composer require laravel/breeze --dev
# Install the Blade stack (traditional)
php artisan breeze:install blade
# Or for API only (SPA/mobile)
php artisan breeze:install api
# Compile assets
npm install && npm run build
# Run migrations (users, sessions tables, etc.)
php artisan migrateBreeze genera rutas de autenticacion, controladores y vistas para registro, inicio de sesion, restablecimiento de contrasena y verificacion de correo electronico.
<?php
// app/Models/User.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
// Relationship: a user owns multiple tasks
public function tasks(): HasMany
{
return $this->hasMany(Task::class);
}
// Shortcut: user's pending tasks
public function pendingTasks(): HasMany
{
return $this->tasks()->pending();
}
// Shortcut: user's overdue tasks
public function overdueTasks(): HasMany
{
return $this->tasks()->overdue();
}
}¿Listo para aprobar tus entrevistas de Laravel?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Creacion de Controladores y Rutas
El controlador orquesta la logica de negocio entre las solicitudes HTTP y el modelo. Laravel 11 promueve controladores de accion unica o recursos RESTful.
<?php
// app/Http/Controllers/TaskController.php
namespace App\Http\Controllers;
use App\Models\Task;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
use Illuminate\Support\Facades\Gate;
class TaskController extends Controller
{
// List tasks for the authenticated user
public function index(Request $request): View
{
// Retrieve tasks with optional filtering
$tasks = $request->user()
->tasks()
->when($request->status, fn($q, $status) => $q->where('status', $status))
->when($request->priority, fn($q, $priority) => $q->where('priority', $priority))
->orderBy('due_date')
->orderBy('priority', 'desc')
->paginate(15);
return view('tasks.index', compact('tasks'));
}
// Creation form
public function create(): View
{
return view('tasks.create');
}
// Store a new task
public function store(Request $request): RedirectResponse
{
// Validate incoming data
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string|max:5000',
'priority' => 'required|in:low,medium,high',
'due_date' => 'nullable|date|after_or_equal:today',
]);
// Create the task linked to the user
$request->user()->tasks()->create($validated);
return redirect()
->route('tasks.index')
->with('success', 'Task created successfully.');
}
// Display a task
public function show(Task $task): View
{
// Verify user ownership
Gate::authorize('view', $task);
return view('tasks.show', compact('task'));
}
// Edit form
public function edit(Task $task): View
{
Gate::authorize('update', $task);
return view('tasks.edit', compact('task'));
}
// Update a task
public function update(Request $request, Task $task): RedirectResponse
{
Gate::authorize('update', $task);
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string|max:5000',
'status' => 'required|in:pending,in_progress,completed',
'priority' => 'required|in:low,medium,high',
'due_date' => 'nullable|date',
]);
// Update completed_at if status = completed
if ($validated['status'] === Task::STATUS_COMPLETED && !$task->completed_at) {
$validated['completed_at'] = now();
}
$task->update($validated);
return redirect()
->route('tasks.index')
->with('success', 'Task updated.');
}
// Delete a task
public function destroy(Task $task): RedirectResponse
{
Gate::authorize('delete', $task);
$task->delete();
return redirect()
->route('tasks.index')
->with('success', 'Task deleted.');
}
// Mark as completed (quick action)
public function complete(Task $task): RedirectResponse
{
Gate::authorize('update', $task);
$task->markAsCompleted();
return back()->with('success', 'Task completed!');
}
}El controlador utiliza Gates para autorizacion, validacion integrada y route model binding para un codigo conciso y seguro.
Definicion de Rutas
Las rutas definen URLs y las asocian con acciones del controlador. Laravel 11 centraliza las rutas en routes/web.php.
<?php
// routes/web.php
use App\Http\Controllers\TaskController;
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
// Public homepage
Route::get('/', function () {
return view('welcome');
});
// Routes protected by authentication
Route::middleware(['auth', 'verified'])->group(function () {
// Dashboard with statistics
Route::get('/dashboard', function () {
$user = auth()->user();
return view('dashboard', [
'totalTasks' => $user->tasks()->count(),
'pendingTasks' => $user->tasks()->pending()->count(),
'completedTasks' => $user->tasks()->completed()->count(),
'overdueTasks' => $user->tasks()->overdue()->count(),
]);
})->name('dashboard');
// RESTful routes for tasks
Route::resource('tasks', TaskController::class);
// Quick action to complete a task
Route::patch('/tasks/{task}/complete', [TaskController::class, 'complete'])
->name('tasks.complete');
// Profile routes (generated by Breeze)
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
// Authentication routes (generated by Breeze)
require __DIR__.'/auth.php';El metodo Route::resource() genera automaticamente las siete rutas RESTful estandar: index, create, store, show, edit, update y destroy.
Configuracion de Politicas de Autorizacion
Las politicas centralizan la logica de autorizacion para cada modelo. Determinan quien puede realizar que acciones sobre los recursos.
# terminal
# Generate the policy for Task
php artisan make:policy TaskPolicy --model=Task<?php
// app/Policies/TaskPolicy.php
namespace App\Policies;
use App\Models\Task;
use App\Models\User;
class TaskPolicy
{
// Check before all methods
// Returning true bypasses all checks (admin)
public function before(User $user, string $ability): ?bool
{
if ($user->is_admin) {
return true;
}
return null; // Continue to specific methods
}
// Can view the list of tasks
public function viewAny(User $user): bool
{
return true; // Any authenticated user
}
// Can view a specific task
public function view(User $user, Task $task): bool
{
return $user->id === $task->user_id;
}
// Can create a task
public function create(User $user): bool
{
return true;
}
// Can update a task
public function update(User $user, Task $task): bool
{
return $user->id === $task->user_id;
}
// Can delete a task
public function delete(User $user, Task $task): bool
{
return $user->id === $task->user_id;
}
// Can restore a soft-deleted task
public function restore(User $user, Task $task): bool
{
return $user->id === $task->user_id;
}
// Can permanently delete
public function forceDelete(User $user, Task $task): bool
{
return $user->id === $task->user_id;
}
}Laravel descubre automaticamente las politicas mediante convenciones de nombres. La TaskPolicy se aplica al modelo Task.
Las politicas son esenciales para la seguridad. Sin ellas, un usuario podria manipular URLs para acceder a las tareas de otros usuarios. Siempre se debe verificar la propiedad de los recursos.
Construccion de una API REST
Laravel simplifica la creacion de APIs RESTful con rutas dedicadas, recursos JSON y Sanctum para autenticacion.
# terminal
# Install Sanctum (included by default since Laravel 11)
php artisan install:api
# This command:
# - Publishes Sanctum migrations
# - Adds the HasApiTokens trait to the User model
# - Configures API routes<?php
// app/Http/Controllers/Api/TaskController.php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\TaskResource;
use App\Http\Resources\TaskCollection;
use App\Models\Task;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
class TaskController extends Controller
{
// Paginated list of tasks
public function index(Request $request): AnonymousResourceCollection
{
$tasks = $request->user()
->tasks()
->when($request->status, fn($q, $s) => $q->where('status', $s))
->when($request->priority, fn($q, $p) => $q->where('priority', $p))
->when($request->boolean('overdue'), fn($q) => $q->overdue())
->latest()
->paginate($request->input('per_page', 15));
return TaskResource::collection($tasks);
}
// Create a task
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string|max:5000',
'priority' => 'required|in:low,medium,high',
'due_date' => 'nullable|date|after_or_equal:today',
]);
$task = $request->user()->tasks()->create($validated);
return response()->json([
'message' => 'Task created successfully.',
'data' => new TaskResource($task),
], 201);
}
// Display a task
public function show(Task $task): TaskResource
{
$this->authorize('view', $task);
return new TaskResource($task);
}
// Update a task
public function update(Request $request, Task $task): TaskResource
{
$this->authorize('update', $task);
$validated = $request->validate([
'title' => 'sometimes|required|string|max:255',
'description' => 'nullable|string|max:5000',
'status' => 'sometimes|required|in:pending,in_progress,completed',
'priority' => 'sometimes|required|in:low,medium,high',
'due_date' => 'nullable|date',
]);
if (isset($validated['status'])
&& $validated['status'] === Task::STATUS_COMPLETED
&& !$task->completed_at) {
$validated['completed_at'] = now();
}
$task->update($validated);
return new TaskResource($task->fresh());
}
// Delete a task
public function destroy(Task $task): JsonResponse
{
$this->authorize('delete', $task);
$task->delete();
return response()->json([
'message' => 'Task deleted successfully.',
]);
}
}Recursos de API para Transformacion de Datos
Los recursos de API transforman modelos Eloquent en respuestas JSON estructuradas, controlando con precision los datos expuestos.
<?php
// app/Http/Resources/TaskResource.php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class TaskResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'description' => $this->description,
'status' => $this->status,
'priority' => $this->priority,
'due_date' => $this->due_date?->format('Y-m-d'),
'is_overdue' => $this->is_overdue,
'completed_at' => $this->completed_at?->toISOString(),
'created_at' => $this->created_at->toISOString(),
'updated_at' => $this->updated_at->toISOString(),
// Conditional inclusion of the user
'user' => $this->whenLoaded('user', fn() => [
'id' => $this->user->id,
'name' => $this->user->name,
]),
];
}
}Rutas de API con Autenticacion Sanctum
<?php
// routes/api.php
use App\Http\Controllers\Api\TaskController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
// Route to retrieve the authenticated user
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
// API routes protected by Sanctum
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('tasks', TaskController::class);
// Dashboard statistics
Route::get('/dashboard/stats', function (Request $request) {
$user = $request->user();
return response()->json([
'total' => $user->tasks()->count(),
'pending' => $user->tasks()->pending()->count(),
'in_progress' => $user->tasks()->inProgress()->count(),
'completed' => $user->tasks()->completed()->count(),
'overdue' => $user->tasks()->overdue()->count(),
]);
});
});Testing Automatizado con Pest
Laravel 11 integra Pest PHP por defecto, un framework de testing elegante y expresivo. Los tests aseguran que la aplicacion funcione correctamente despues de cada modificacion.
<?php
// tests/Feature/TaskTest.php
use App\Models\Task;
use App\Models\User;
// Test: a user can view their tasks
test('user can view their tasks', function () {
$user = User::factory()->create();
$tasks = Task::factory()->count(5)->for($user)->create();
$response = $this->actingAs($user)->get('/tasks');
$response->assertOk();
$response->assertViewHas('tasks');
});
// Test: a user can create a task
test('user can create a task', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/tasks', [
'title' => 'New task',
'description' => 'Task description',
'priority' => 'high',
'due_date' => now()->addDays(7)->format('Y-m-d'),
]);
$response->assertRedirect('/tasks');
$this->assertDatabaseHas('tasks', [
'user_id' => $user->id,
'title' => 'New task',
'priority' => 'high',
]);
});
// Test: title validation required
test('task title is required', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/tasks', [
'title' => '',
'priority' => 'medium',
]);
$response->assertSessionHasErrors('title');
});
// Test: a user cannot view another user's task
test('user cannot view another users task', function () {
$user = User::factory()->create();
$otherUser = User::factory()->create();
$task = Task::factory()->for($otherUser)->create();
$response = $this->actingAs($user)->get("/tasks/{$task->id}");
$response->assertForbidden();
});
// Test: mark a task as completed
test('user can complete a task', function () {
$user = User::factory()->create();
$task = Task::factory()->for($user)->create(['status' => 'pending']);
$response = $this->actingAs($user)->patch("/tasks/{$task->id}/complete");
$response->assertRedirect();
$task->refresh();
expect($task->status)->toBe('completed');
expect($task->completed_at)->not->toBeNull();
});
// API test: retrieve tasks
test('api returns paginated tasks', function () {
$user = User::factory()->create();
Task::factory()->count(20)->for($user)->create();
$response = $this->actingAs($user, 'sanctum')
->getJson('/api/tasks?per_page=10');
$response->assertOk()
->assertJsonCount(10, 'data')
->assertJsonStructure([
'data' => [
'*' => ['id', 'title', 'status', 'priority', 'due_date'],
],
'links',
'meta',
]);
});Ejecucion de tests con Pest.
# terminal
# Run all tests
php artisan test
# Tests with code coverage
php artisan test --coverage
# Tests for a specific file
php artisan test tests/Feature/TaskTest.php
# Parallel tests (faster)
php artisan test --parallelOptimizaciones para Produccion
Antes del despliegue, varias optimizaciones mejoran el rendimiento de la aplicacion Laravel.
# terminal
# Cache configuration
php artisan config:cache
# Cache routes
php artisan route:cache
# Cache views
php artisan view:cache
# Optimize Composer autoloader
composer install --optimize-autoloader --no-dev
# Generate asset manifest
npm run buildConfiguracion del Archivo .env para Produccion
# .env.production
APP_NAME="Task Manager"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://taskmanager.example.com
# Cache and session via Redis (recommended)
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=task_manager_prod
DB_USERNAME=app_user
DB_PASSWORD=strong_password_here
# Mail
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=postmaster@taskmanager.example.com
MAIL_PASSWORD=secret
MAIL_ENCRYPTION=tlsEl archivo .env nunca debe incluirse en el repositorio Git. Se recomienda usar .env.example como plantilla y configurar las variables sensibles directamente en el servidor de produccion o mediante gestores de secretos.
Conclusion
Laravel 11 simplifica significativamente el desarrollo moderno en PHP con su estructura optimizada y convenciones inteligentes. Esta guia cubrio los fundamentos para construir una aplicacion completa: modelos Eloquent, autenticacion, controladores RESTful, politicas de autorizacion, API con Sanctum y tests automatizados.
Lista de Verificacion para Aplicaciones Laravel de Calidad
- ✅ Usar migraciones para todas las modificaciones del esquema
- ✅ Implementar politicas para asegurar el acceso a recursos
- ✅ Validar toda la entrada del usuario en los controladores
- ✅ Usar recursos de API para controlar los datos expuestos
- ✅ Escribir tests para las funcionalidades criticas
- ✅ Configurar cache y optimizaciones antes del despliegue
- ✅ Proteger las variables de entorno sensibles
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
El ecosistema de Laravel continua evolucionando con paquetes como Livewire para interfaces reactivas, Horizon para gestion de colas y Octane para un rendimiento excepcional. Dominar estos fundamentos abre la puerta a aplicaciones PHP robustas y mantenibles.
Etiquetas
Compartir
Artículos relacionados

Symfony 7: API Platform y Mejores Practicas
Guia completa de API Platform 4 con Symfony 7. Recursos, grupos de serializacion, state processors, filtros, seguridad y pruebas automatizadas para APIs REST profesionales.

NestJS: Construir una API REST Completa
Tutorial completo para construir una API REST profesional con NestJS. Controladores, Servicios, Modulos, validacion con class-validator y manejo centralizado de errores.