CRUD: Aplicación API de librería de Laravel

crud

Aplicación API de librería que usa la operación CRUD de Laravel 8

En este artículo, implementaremos una aplicación de librería Laravel 8 CRUD. Después de leer este artículo, podrá:

  • Aprender a crear tablas en la base de datos MySQL con relaciones de One-to-Many y de Many-to-Many.
  • Implementar los modelos y las funciones a las que sirven.
  • Saber cómo implementar CRUD con Laravel Resouce Controller
  • Familiarízate con los métodos belongsTobelongsToMany

Una definición simple del proyecto.

Cada libro pertenece a una editorial, por lo que podrá asignar su libro a una editorial específica o, si no puede encontrarlo, puede almacenar una nueva editorial en la base de datos.

Además de eso, puede almacenar autores en la tabla de autores e incluir uno de ellos o varios de ellos en su libro como el nombre del autor. Si no desea leer este texto completo, puede omitirlo y encontrar el código completo en el repositorio en el que inserto el enlace al final del artículo.


Echemos un vistazo a cómo está diseñada nuestra base de datos, luego profundizaremos en el código.

crud
Estructura de la base de datos

Relación uno a muchos

Existe una relación de uno a muchos entre libros y editores. Un elemento de los editores puede estar vinculado a muchos elementos de los libros, pero un miembro de los libros está vinculado a un solo elemento de los editores.

Relación de muchos a muchos

Existe una relación de muchos a muchos entre libros y autores.

Cada libro ha pertenecido a muchos autores y cada autor ha escrito muchos libros. Entonces, en esta situación, usaremos una tabla como tabla dinámica para almacenar relaciones. Nuestra tabla dinámica es book_authors.


Crear tablas en la base de datos

Después de crear el proyecto con el compositor, es hora de crear nuestras tablas y migrarlas a la base de datos. En Laravel, usaremos el siguiente comando para crear la tabla.

php artisan make:migration create_publishers_table

En primer lugar, crearemos la tabla de editores que tendrá la siguiente estructura:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePublishersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('publishers', function (Blueprint $table) {
            $table->id();
            $table->string('identifier')->unique();
            $table->string('fname');
            $table->string('lname');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('publishers');
    }
}

Y aquí está el resto de las tablas que debe crear en orden:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBooksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
            $table->id();
            $table->string('isbn')->unique();
            $table->string('name');
            $table->string('year');
            $table->integer('page');
            $table->foreignId('publisher_id')->constrained('publishers');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('books');
    }
}
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAuthorsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('authors', function (Blueprint $table) {
            $table->id();
            $table->string('identifier')->uniqu();
            $table->string('fname');
            $table->string('lname');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('authors');
    }
}
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBookAuthorsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('book_authors', function (Blueprint $table) {
            $table->id();
            $table->foreignId('book_id')->constrained('books');
            $table->foreignId('author_id')->constrained('authors');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('book_authors');
    }
}

Crear modelos

En esta sección, hablaremos de los modelos y las funciones que cumplen. Primero, crearemos el modelo Publisher con el comando php artisan make:model Publisher y, como puede ver en la estructura de la base de datos, existe una relación de uno a muchos entre libros y editores, y esto se define mediante la definición de un método en el modelo Eloquent.

<?php

namespace App\Models;

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

class Publisher extends Model
{
    use HasFactory;

    /**
     * @var array
     */
    protected $fillable = [
        'identifier',
        'fname',
        'lname',
    ];
    
    /**
     * publisher's books
     *
     * @return void
     */
    public function books(){
        return $this->hasMany(Book::class,'publisher_id');
    }
}

Es hora de crear otros modelos y definir la relación de varios a varios entre libros y autores. así que crearemos el modelo BookAuthor como un pivote Eloquent. Para hacer esto, asegúrese de que su modelo se extienda Illuminate\Database\Eloquent\Relations\Pivot

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\Pivot;

class BookAuthor extends Pivot
{
    use HasFactory;

    protected $table = 'book_authors';
}

Y ahora crearemos el modelo Author, que contendrá la función con el nombre de los libros que usa el método  belongsToMany y el modelo BookAuthor como modelo  Pivot para obtener la lista de todos los libros del objeto autor.

<?php

namespace App\Models;

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

class Author extends Model
{
    use HasFactory;

    /**
     * @var array
     */
    protected $fillable = [
        'identifier',
        'fname',
        'lname',
    ];
    
    /**
     * authors's books
     *
     * @return void
     */
    public function books(){
        return $this->belongsToMany(Author::class,'book_authors')->using(BookAuthor::class);
    }
}

Finalmente, tendremos el modelo de libro que usa el método belongsTo para obtener el editor relacionado en la función con el nombre del editor y utiliza el método  belongsToMany y el modelo BookAuthor como modelo dinámico para obtener la lista de todos los autores del objeto libro.

<?php

namespace App\Models;

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

class Book extends Model
{
    use HasFactory;

    /**
     * @var array
     */
    protected $fillable = [
        'isbn',
        'name',
        'year',
        'page',
        'publisher_id',
    ];

    /**
     * book's publisher
     *
     * @return void
     */
    public function publisher()
    {
        return $this->belongsTo(Publisher::class)
            ->withDefault([
                'identifier' => 'WITHOUT ID',
                'fname' => 'NOT FOUND',
                'lname' => 'NOT FOUND',
            ]);
    }

    /**
     * book's authors
     *
     * @return void
     */
    public function authors()
    {
        return $this->belongsToMany(Author::class, 'book_authors')
            ->using(BookAuthor::class);
    }
}

Crear controladores

  • En este proyecto, cada controlador contendrá las siguientes funciones:
  • La función de índice se utilizará para obtener una lista completa de todos los registros.
  • En la función de tienda después de validar las solicitudes, se creará el nuevo elemento.
  • La función show se usa para obtener el elemento específico pasando ID a la función.
  • Pasamos el ID a la función de destrucción y luego detectamos elementos con el ID dado y luego lo borramos.
  • La función de actualización se utiliza para actualizar el elemento dado en función de las solicitudes dadas.
<?php

namespace App\Http\Controllers;

use App\Models\Publisher;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;

class PublisherController extends Controller
{
    /**
     * @var Model
     */
    protected $model;

    /**
     * __construct
     *
     * @param  Publisher $publisher
     * @return void
     */
    public function __construct(Publisher $publisher)
    {
        $this->model = $publisher;
    }

    /**
     * @return Collection
     */
    public function index()
    {
        $items = $this->model->with('books')->get();
        return response(['data' => $items, 'status' => 200]);
    }

    /**
     * @param  Request $request
     * @return Collection
     */
    public function store(Request $request)
    {
        $request->validate([
            'identifier' => 'required|unique:publishers|min:3',
            'fname' => 'required',
            'lname' => 'required',
        ]);

        $this->model->create($request->all());
        return $this->index();
    }

    /**
     * @param  mixed $id
     * @return Collection
     */
    public function destroy($id)
    {
        try {
            $item = $this->model->with('books')->findOrFail($id);
            $item->delete();
            return $this->index();
        } catch (ModelNotFoundException $e) {
            return response(['message' => 'Item Not Found!', 'status' => 404]);
        }
    }

    /**
     * @param  mixed $id
     * @return Model
     */
    public function show($id)
    {
        try {
            $item = $this->model->with('books')->findOrFail($id);
            return response(['data' => $item, 'status' => 200]);
        } catch (ModelNotFoundException $e) {
            return response(['message' => 'Item Not Found!', 'status' => 404]);
        }
    }

    /**
     * @param  mixed $id
     * @param  mixed $request
     * @return Collection
     */
    public function update($id, Request $request)
    {
        try {
            $item = $this->model->with('books')->findOrFail($id);
            $item->update($request->all());
            return response(['data' => $item, 'status' => 200]);
        } catch (ModelNotFoundException $e) {
            return response(['message' => 'Item Not Found!', 'status' => 404]);
        }
    }
}

En la función de actualización y almacenamiento de BookController, verá el código $item->authors()->sync($requeest->get('authors')).  sync se puede utilizar para sincronizar ambos lados de una relación belongsToMany. De forma predeterminada, descarta todas las relaciones y solo mantiene las proporcionadas como argumentos. De manera similar, si desea eliminar una determinada relación de entidad de la tabla dinámica, puede usar el método. Por ejemplo, si desea eliminar a los autores de un libro, puede hacerlo con el código $item->authors()->detach().

<?php

namespace App\Http\Controllers;

use App\Models\Book;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;

class BookController extends Controller
{
    /**
     * @var Model
     */
    protected $model;

    /**
     * @param  Model $book
     * @return void
     */
    public function __construct(Book $book)
    {
        $this->model = $book;
    }

    /**
     * @return Collection
     */
    public function index()
    {
        $items = $this->model->with('authors', 'publisher')->get();
        return response(['data' => $items, 'status' => 200]);
    }

    /**
     * @param  Request $request
     * @return Collection
     */
    public function store(Request $request)
    {
        $request->validate([
            'isbn' => 'required|digits:13|integer|unique:books,isbn',
            'name' => 'required|min:3',
            'year' => 'required|integer|digits:4',
            'page' => 'required|integer',
            'publisher_id' => 'exists:publishers,id',
            'authors' => 'array',
            'authors.*' => 'exists:authors,id'
        ]);
        $item = $this->model->create($request->all());

        $authors = $request->get('authors');
        $item->authors()->sync($authors);

        return $this->index();
    }

    /**
     * @param  mixed $id
     * @return Collection
     */
    public function destroy($id)
    {
        try {
            $item = $this->model->with('authors', 'publisher')->findOrFail($id);
            $item->authors()->detach();
            $item->delete();
            return $this->index();
        } catch (ModelNotFoundException $e) {
            return response(['message' => 'Item Not Found!', 'status' => 404]);
        }
    }

    /**
     * @param  mixed $id
     * @return Model
     */
    public function show($id)
    {
        try {
            $item = $this->model->with('authors', 'publisher')->findOrFail($id);
            return response(['data' => $item, 'status' => 200]);
        } catch (ModelNotFoundException $e) {
            return response(['message' => 'Item Not Found!', 'status' => 404]);
        }
    }

    /**
     * @param  mixed $id
     * @param  mixed $request
     * @return Collection
     */
    public function update($id, Request $request)
    {
        try {
            $item = $this->model->with('authors', 'publisher')->findOrFail($id);
            $item->update($request->all());

            $authors = $request->get('authors');
            $item->authors()->sync($authors);

            return response(['data' => $item, 'status' => 200]);
        } catch (ModelNotFoundException $e) {
            return response(['message' => 'Item Not Found!', 'status' => 404]);
        }
    }
}
<?php

namespace App\Http\Controllers;

use App\Models\Author;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;

class AuthorController extends Controller
{
    /**
     * @var Model
     */
    protected $model;

    /**
     * @param  Model $author
     * @return void
     */
    public function __construct(Author $author)
    {
        $this->model = $author;
    }

    /**
     * @return Collection
     */
    public function index()
    {
        $items = $this->model->with('books')->get();
        return response(['data' => $items], 200);
    }

    /**
     * @param  Request $request
     * @return Collection
     */
    public function store(Request $request)
    {
        $request->validate([
            'identifier' => 'required|unique:authors|min:3',
            'fname' => 'required',
            'lname' => 'required',
        ]);

        $this->model->create($request->all());
        return $this->index();
    }

    /**
     * @param  mixed $id
     * @return Collection
     */
    public function destroy($id)
    {
        try {
            $item = $this->model->with('books')->findOrFail($id);
            $item->books()->detach();
            $item->delete();
            return $this->index();
        } catch (ModelNotFoundException $e) {
            return response(['message' => 'Item Not Found!', 'status' => 404]);
        }
    }

    /**
     * @param  mixed $id
     * @return Model
     */
    public function show($id)
    {
        try {
            $item = $this->model->with('books')->findOrFail($id);
            return response(['data' => $item, 'status' => 200]);
        } catch (ModelNotFoundException $e) {
            return response(['message' => 'Item Not Found!', 'status' => 404]);
        }
    }

    /**
     * @param  mixed $id
     * @param  mixed $request
     * @return Collection
     */
    public function update($id, Request $request)
    {
        try {
            $item = $this->model->with('books')->findOrFail($id);
            $item->update($request->all());
            return response(['data' => $item, 'status' => 200]);
        } catch (ModelNotFoundException $e) {
            return response(['message' => 'Item Not Found!', 'status' => 404]);
        }
    }
}

Crear rutas

Finalmente, definiremos nuestras rutas en el directorio routes/api.php. Usando Route:apiResource configura algunas rutas predeterminadas. Y puede ver la lista de todas las rutas ejecutando el comandode Artisan route:list.

<?php

use App\Http\Controllers\AuthorController;
use App\Http\Controllers\BookController;
use App\Http\Controllers\PublisherController;
use Illuminate\Support\Facades\Route;

Route::apiResource('authors', AuthorController::class);
Route::apiResource('publishers', PublisherController::class);
Route::apiResource('books',BookController::class);

Ahora tienes una API de librería. El código completo se encuentra en el siguiente repositorio.

https://github.com/hanieas/Tutorial-Book-Store-API

Espero que este curso lo ayude a familiarizarse con el diseño de bases de datos, las relaciones de uno a muchos y las relaciones de muchos-a-muchos en Laravel.

 

Recent Post