Tutoría de Programación – Primer CRUD con Laravel

Empecemos creando un proyecto de cero:

laravel new segundaApp

Instalamos dependencias de Node.Js y compilamos el front-end

npm install && npm run build

Levantamos el servidor para ver que este todo OK:

php artisan serve

En caso de tener problemas con bases de datos, siempre es recomendable dirigirnos a nuestro motor de bases de datos, y crear una base de dato con el nombre que nos indica el archivo .env en el directorio raíz del proyecto que vimos la clase anterior.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=segundaApp
DB_USERNAME=usuario
DB_PASSWORD=contraseña

Luego de crear la base de datos manual, ejecutamos la primer migración para que se ejecute la creación de las tablas básicas de la base de datos:

php artisan migrate

Vamos a empezar a trabajar en un pequeño proyecto CRUD utilizando el patrón de diseño que nos proporciona Laravel, para eso vamos a ejecutar el comando:

php artisan make:model Post -mcr

Esto nos va a crear lo siguiente:

  • Modelo Post en app/Models
  • Migración de la tabla posts en database/migrations ( ubicado en: database/migrations/…create_posts_table.php)
  • Controlador PostController en app/Http/Controllers con métodos CRUD

Laravel se encarta de trabajar por nosotros, pero no es ningún mago, los magos somos nosotros aprendiendo a manejar cada una de las partes del framework. Vamos a editar la migración (database/migrations/…create_posts_table.php):

public function up(): void
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('titulo');
        $table->text('contenido');
        $table->timestamps();
    });
}

Y vamos a ejecutar una migración para refrescar los cambios:

php artisan migrate

Vamos a configurar las rutas en routes/web.php

use App\Http\Controllers\PostController;

Route::resource('posts', PostController::class);

A tener en cuenta:

  • Laravel ya genera las rutas necesarias (/posts, /posts/create, /posts/{id}/edit, etc.)
  • Solo faltaría agregar vistas Blade para interactuar con nuestra app.

Ya creamos el modelo, migración y controlador básico para empezar a trabajar, pero tenemos que empezar a darles vida, para eso, vamos a empezar a definir el modelo, abrimos app/Models/Post.php y agregamos el atributo $fillable para asignación masiva:

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $fillable = ['titulo', 'contenido'];
}

Configuramos las rutas para nuestra app editando el archivo routes/web.php:

use App\Http\Controllers\PostController;

Route::get('/', function () {
    return redirect()->route('posts.index');
});

Route::resource('posts', PostController::class);

Abrimos el archivo app/Http/Controllers/PostController.php y completamos los métodos:

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::latest()->paginate(5);
        return view('posts.index', compact('posts'));
    }

    public function create()
    {
        return view('posts.create');
    }

    public function store(Request $request)
    {
        $request->validate([
            'titulo' => 'required|max:255',
            'contenido' => 'required'
        ]);

        Post::create($request->all());

        return redirect()->route('posts.index')->with('success', 'Post creado correctamente.');
    }

    public function edit(Post $post)
    {
        return view('posts.edit', compact('post'));
    }

    public function update(Request $request, Post $post)
    {
        $request->validate([
            'titulo' => 'required|max:255',
            'contenido' => 'required'
        ]);

        $post->update($request->all());

        return redirect()->route('posts.index')->with('success', 'Post actualizado correctamente.');
    }

    public function destroy(Post $post)
    {
        $post->delete();
        return redirect()->route('posts.index')->with('success', 'Post eliminado correctamente.');
    }
}

Creamos la carpeta resources/views/posts y los siguientes archivos dentro de ella:

Layout base (resources/views/layout.blade.php):

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>CRUD de Posts</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">

<div class="container py-4">
    @if (session('success'))
        <div class="alert alert-success">
            {{ session('success') }}
        </div>
    @endif

    @yield('content')
</div>

</body>
</html>

Listado de posts ubicado en resources (resources/views/posts/index.blade.php):

@extends('layout')

@section('content')
<div class="d-flex justify-content-between align-items-center mb-3">
    <h1>Listado de Posts</h1>
    <a href="{{ route('posts.create') }}" class="btn btn-primary">Nuevo Post</a>
</div>

<table class="table table-bordered">
    <thead>
        <tr>
            <th>Título</th>
            <th>Contenido</th>
            <th>Acciones</th>
        </tr>
    </thead>
    <tbody>
        @forelse ($posts as $post)
            <tr>
                <td>{{ $post->titulo }}</td>
                <td>{{ Str::limit($post->contenido, 50) }}</td>
                <td>
                    <a href="{{ route('posts.edit', $post) }}" class="btn btn-warning btn-sm">Editar</a>
                    <form action="{{ route('posts.destroy', $post) }}" method="POST" style="display:inline">
                        @csrf @method('DELETE')
                        <button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('¿Eliminar este post?')">Eliminar</button>
                    </form>
                </td>
            </tr>
        @empty
            <tr>
                <td colspan="3">No hay posts disponibles.</td>
            </tr>
        @endforelse
    </tbody>
</table>

{{ $posts->links() }}
@endsection

Crear posts también en resources (resources/views/posts/create.blade.php):

@extends('layout')

@section('content')
<h1>Crear nuevo Post</h1>

@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

<form action="{{ route('posts.store') }}" method="POST">
    @csrf
    <div class="mb-3">
        <label for="titulo" class="form-label">Título</label>
        <input type="text" name="titulo" class="form-control" value="{{ old('titulo') }}">
    </div>
    <div class="mb-3">
        <label for="contenido" class="form-label">Contenido</label>
        <textarea name="contenido" class="form-control">{{ old('contenido') }}</textarea>
    </div>
    <button type="submit" class="btn btn-success">Guardar</button>
    <a href="{{ route('posts.index') }}" class="btn btn-secondary">Cancelar</a>
</form>
@endsection

Editar post (resources/views/posts/edit.blade.php):

@extends('layout')

@section('content')
<h1>Editar Post</h1>

@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

<form action="{{ route('posts.update', $post) }}" method="POST">
    @csrf @method('PUT')
    <div class="mb-3">
        <label for="titulo" class="form-label">Título</label>
        <input type="text" name="titulo" class="form-control" value="{{ old('titulo', $post->titulo) }}">
    </div>
    <div class="mb-3">
        <label for="contenido" class="form-label">Contenido</label>
        <textarea name="contenido" class="form-control">{{ old('contenido', $post->contenido) }}</textarea>
    </div>
    <button type="submit" class="btn btn-success">Actualizar</button>
    <a href="{{ route('posts.index') }}" class="btn btn-secondary">Cancelar</a>
</form>
@endsection

Nos quedaria probar la aplicación y empezar a detectar errores

php artisan serve

Si bien en una clase vemos muchos temas nuevos, no se preocupen, la idea de esta práctica es ver lo fácil que se puede hacer una app en laravel con poco. Luego de montar esta aplicación en Laravel, vamos a empezar a desglozar paso por paso para poder entender bien su funcionamiento, nos vemos la próxima :).

Deja un comentario

I’m Luis E. Fagúndez

Bienvenidos a mi página web personal. Mi nombre es Luis, me gusta enseñar, programar y tomar litros y litros de café.

En esta web vas a encontrar materiales sobre educación, programación, Gnu/Linux, software libre y mucho más.

Esta web busca brindar información sobre las asignaturas que imparto en DGETP-UTU, así como proyectos personales y otras cosas.

Gracias por ser parte de esta hermosa comunidad.

Formas de contacto