Build a library app with Laravel 10 and MySQL
Learn how to build a Laravel application backed by a MySQL PlanetScale database.

Laravel is arguably the most popular PHP framework available today. It provides tooling that enables developers to implement complex application features, such as authentication and rate limiting, with single commands. It also integrates seamlessly with MySQL-powered databases — a popular and reliable database management system.
In this tutorial, I will show you how to build a library app using Laravel and MySQL with PlanetScale. PlanetScale is a cloud-native, MySQL compatible database platform. It makes it easy to scale MySQL horizontally while providing features like high availability through automatic replication, and database branching with zero-downtime schema migrations.
The app will have two features: authentication and loan management. The loan management feature enables logged-in users to borrow and return as many books as possible. This feature also implements checks to prevent the "over-borrowing" of books.
Get started
To follow this tutorial, you will need the following:
- A basic understanding of PHP and Laravel
- PHP (version ≥ 8.1) installed
- Composer installed globally
- A free PlanetScale account
Set up the PlanetScale database
In your PlanetScale dashboard, click on New database, specify a database name (for example, library-app
), and click Create database to complete the process. You will be redirected to the database overview.
While the provisioning process of the database is ongoing, you can optionally configure your database to automatically copy migration data to new branches as they are created . Go to the Settings section and select the option to Automatically copy migration data. Make sure you specify Laravel for the migration framework.
With this feature enabled, the migration metadata will also be transferred to the production branch when changes are merged in. You can read more about it in our docs.
Back in the Overview section, click the “Get connection strings” button in the right column of the dashboard. This shows you the database authentication credentials. Select Laravel and copy the credentials shown in the sample .env file. Be careful with this information as it is only shown once. If you lose the credentials, you will have to recreate them.
With your database in place, it’s time to build your application.
Scaffold the application
Create a new application using the following command:
composer create-project laravel/laravel library-app
When the project setup is complete, open the library-app
project in your preferred IDE or text editor.
To connect your application to your PlanetScale database, update the following .env variables to match the ones you copied earlier. Make sure to leave the other values as they are.
DB_CONNECTION=mysql
DB_HOST=<YOUR_DB_HOST>
DB_PORT=3306
DB_DATABASE=<YOUR_DB_DATABASE>
DB_USERNAME=<YOUR_DB_USERNAME>
DB_PASSWORD=<YOUR_DB_PASSWORD>
MYSQL_ATTR_SSL_CA=/etc/ssl/cert.pem
Run the following command to check for a successful connection to the database, create database tables, and publish its schema.
php artisan migrate
Navigate to your PlanetScale dashboard and click Tables to view the database tables for your application.
Add authentication
Laravel Breeze will be used to implement authentication. Install it with the following command.
composer require laravel/breeze --dev
Laravel Breeze makes provisioning for various “stack” flavours ranging from React/Vue to Inertia SSR. We will use the default, which is the Blade stack. Install Laravel Breeze as shown below.
php artisan breeze:install
You will be prompted to respond to few questions. Choose blade
as the stack and response with no
to using dark mode support and Pest.
Next, issue the following command to update the database and install frontend dependencies;
php artisan migrate
npm install
With just two commands, you have written and published the authentication views, routes, controllers, and other resources to your application.
Add the models
In addition to the User
model created automatically by Laravel Breeze, we’ll need two other models: Book
and Loan
.
Create the Book
model using the following command:
php artisan make:model Book -cfms
The c
argument tells Artisan to create a controller in addition to the model. In the same way, f
is for factory, m
is for migration, and s
is for seeder. This will allow you to set your application up with pre-saved books.
Next, create the Loan
model using the following command.
php artisan make:model Loan -mc
For the Loan
model, only a controller and migration are required alongside the Book
model.
Update the migrations
Next, update the migrations for the Book
and Loan
models. Laravel migrations are found in the database/migrations
folder. The migration name is prepended with a timestamp in the YYYY_MM_DD_HHMMSS
format. To keep things simple, the timestamp will be skipped when migration filenames are discussed.
Start by updating the migration for the Book
model. Open database/migrations/create_books_table.php
and update the up
function to match the following.
Schema::create('books', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('author');
$table->string('year');
$table->integer('copies_in_circulation');
$table->timestamps();
});
This adds three string columns: title
, author
, year
(the year the book was released), and an integer column named copies_in_circulation
which indicates how many copies are in the library’s possession.
Next, open database/migrations/create_loans_table.php
and update the up
function as shown below.
Schema::create('loans', function (Blueprint $table) {
$table->id();
$table->foreignId('book_id');
$table->foreignId('user_id');
$table->integer('number_borrowed');
$table->dateTime('return_date');
$table->boolean('is_returned')->default(false);
$table->timestamps();
});
Update the factory
The next step is to update the Book factory. Open database/factories/BookFactory.php
and update the definition
function to match the following.
return [
'title' => fake()->sentence(),
'author' => fake()->name,
'year' => fake()->year(),
'copies_in_circulation' => fake()->numberBetween(1, 20)
];
Update the seeder
First, add the import statement for the Book
model if your IDE did not already do so automatically.
use App\Models\Book;
Next, update the seeder for the Book model. Open database/seeders/BookSeeder.php
and add the following to the run
function.
Book::factory()->times(20)->create();
Also, within the database/seeders/DatabaseSeeder.php
, Add the import statement for the User
model if it’s not present.
use App\Models\User;
Next, open database/seeders/DatabaseSeeder.php
and update the run
function as shown below.
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
$this->call([BookSeeder::class]);
Run the new migrations and seed your database using the following command:
php artisan migrate --seed
Your PlanetScale database will be populated with 20 new books as shown below.
Fillable properties, relationships, helper functions
Next, update your models to include fillable properties, relationships, and helper functions. This will make for better separation of concerns and slimmer controller functions.
Start with the User
model created by Laravel Breeze. Open app/Models/User.php
and add this import statement:
use Illuminate\Database\Eloquent\Relations\HasMany;
Then, update the content by adding the following functions to it.
public function activeLoans() {
return $this
->loans()
->where('is_returned', false)
->get();
}
public function loans(): HasMany {
return $this->hasMany(Loan::class);
}
The loans
function is used to specify the relationship between the User
model and the Loan
model as a user can have more than one loan.
The activeLoans
function will be used in the controller (or blade template) to access the books which the user has borrowed.
Next, update the Book
model. Open app/Models/Book.php
and update the code to match the following.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Book extends Model {
use HasFactory;
public function canBeBorrowed(): bool {
return $this->activeLoans() < $this->copies_in_circulation;
}
private function activeLoans(): int {
return $this->loans()
->where('is_returned', false)
->get()
->sum('number_borrowed');
}
public function loans(): HasMany {
return $this->hasMany(Loan::class);
}
public function availableCopies(): int {
return $this->copies_in_circulation - $this->activeLoans();
}
}
The canBeBorrowed
function returns a boolean to indicate whether or not there are still copies available for users to borrow.
The loans
function is used to specify the relationship between the Book
and Loan
models. Depending on the number of books in the library, it is possible for more than one loan to be taken out for a book.
The activeLoans
function uses the loans
relationship to retrieve all the loans associated with a book and get only the ones which have is_returned
set to false.
The last function, availableCopies
, returns the number of copies still available to be borrowed.
Next, update the Loan
model by changing the code in app/Models/Loan.php
to match the following:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Loan extends Model {
use HasFactory;
protected $fillable = [
'number_borrowed',
'return_date',
'book_id',
'user_id',
];
public function user(): BelongsTo {
return $this->belongsTo(User::class);
}
public function book(): BelongsTo {
return $this->belongsTo(Book::class);
}
public function terminate() {
$this->is_returned = true;
$this->save();
}
}
Here, the fillable
property is specified. In addition to that, the inverse relation between the Loan
and User
models and Loan
and Book
models are also included.
The terminate
function is also added, which will be used to terminate a loan when the user returns the associated book.
Finalizing the controllers
Next, add functions to handle incoming requests in your controllers. Start with the BookController
. Open app/Http/Controllers/BookController.php
and update the code to match the following.
<?php
namespace App\Http\Controllers;
use App\Models\Book;
use Illuminate\Contracts\View\View;
class BookController extends Controller {
public function index(): View {
return view('books.index', ['books' => Book::all()]);
}
}
The controller has only one function (index
), which returns all the books in the library.
Next, open app/Http/Controllers/LoanController.php
and update the code to match the following.
<?php
namespace App\Http\Controllers;
use App\Models\Book;
use App\Models\Loan;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator as ValidatorFacade;
use Illuminate\Validation\Validator;
use Illuminate\View\View;
class LoanController extends Controller {
public function index(): View {
return view('loans.index', ['loans' => Auth::user()->activeLoans()]);
}
public function store(Book $book, Request $request): RedirectResponse {
$validator = ValidatorFacade::make($request->all(), [
'number_borrowed' => 'required|int',
'return_date' => 'required',
]);
$validator->after(function (Validator $validator) use ($book) {
$numberBorrowed = $validator->safe()->number_borrowed;
$availableCopies = $book->availableCopies();
if ($numberBorrowed > $availableCopies) {
$validator->errors()->add(
'number_borrowed',
"You cannot borrow more than {$availableCopies} book(s)"
);
}
});
if ($validator->fails()) {
return to_route('loans.create', ['book' => $book])
->withErrors($validator)
->withInput();
}
$loanDetails = $validator->safe()->only([
'number_borrowed',
'return_date',
]);
$loanDetails['book_id'] = $book->id;
$loanDetails['user_id'] = Auth::user()->id;
Loan::create($loanDetails);
return to_route('loans.index')
->with('status', 'Book borrowed successfully');
}
public function create(Book $book): View {
return view('loans.create', ['book' => $book]);
}
public function terminate(Loan $loan): RedirectResponse {
$loan->terminate();
return to_route('loans.index')
->with('status', 'Book returned successfully');
}
}
The index
function returns all the loans for the logged-in user using the Auth
facade and the activeLoans
function declared earlier.
The store
function takes a Book (obtained via Route Model Binding) and a request and creates a new Loan for the logged-in user. The provided request is validated to ensure that the number of books to be borrowed and the return date are specified. It then runs another check to make sure that the number of books to be borrowed is not more than the copies available. This is because while a certain number of books may be in circulation, some loans may already have been created for some of the copies in circulation. If all checks pass, a new loan is created and the user is redirected to a page showing all the active loans. Otherwise, the user is redirected to the loan form where the errors will be rendered accordingly.
The create
function takes a Book (also obtained via Route Model Binding) and returns a form which the user must fill and submit to complete the borrowing process.
Finally, the terminate
function takes a Loan and calls the terminate
function on that loan which effectively makes it available for future loans. The user is also redirected to the list of active loans.
With the controllers in place, the next thing to do is create and update the application views (blade templates).
Update views
In this section, you will modify the default navigation bar included by Laravel breeze and create views for listing, borrowing and viewing books that you have borrowed.
Update navigation
In addition to adding authentication to the application, Laravel Breeze also added a navigation bar that, with some minor adjustments, can be used in your application. Open resources/views/layouts/navigation.blade.php
and replace the content with the following code.
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
<!-- Primary Navigation Menu -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<!-- Logo -->
<div class="shrink-0 flex items-center">
<a href="{{ route('dashboard') }}">
<x-application-logo class="block h-9 w-auto fill-current text-gray-800" />
</a>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</x-nav-link>
</div>
</div>
<!-- Settings Dropdown -->
<div class="hidden sm:flex sm:items-center sm:ml-6">
<x-dropdown align="right" width="48">
<x-slot name="trigger">
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
<div>{{ Auth::user()->name }}</div>
<div class="ml-1">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</div>
</button>
</x-slot>
<x-slot name="content">
<x-dropdown-link :href="route('books.index')">
{{ __('Books') }}
</x-dropdown-link>
<x-dropdown-link :href="route('loans.index')">
{{ __('Loans') }}
</x-dropdown-link>
<x-dropdown-link :href="route('profile.edit')">
{{ __('Profile') }}
</x-dropdown-link>
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-dropdown-link :href="route('logout')"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-dropdown-link>
</form>
</x-slot>
</x-dropdown>
</div>
<!-- Hamburger -->
<div class="-mr-2 flex items-center sm:hidden">
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out">
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- Responsive Navigation Menu -->
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
<div class="pt-2 pb-3 space-y-1">
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</x-responsive-nav-link>
</div>
<!-- Responsive Settings Options -->
<div class="pt-4 pb-1 border-t border-gray-200">
<div class="px-4">
<div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
</div>
<div class="mt-3 space-y-1">
<x-responsive-nav-link :href="route('profile.edit')">
{{ __('Profile') }}
</x-responsive-nav-link>
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-responsive-nav-link :href="route('logout')"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-responsive-nav-link>
</form>
</div>
</div>
</div>
</nav>
Next, create a new folder named books
within the resources/views
folder and then create a new file within it. You can call the file index.blade.php
. Open the newly created file and update its content as shown here:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet"/>
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="font-sans antialiased">
<div class="min-h-screen">
@if (Route::has('login'))
<div class="p-6 text-right">
@auth
@include('layouts.navigation')
@else
<a href="{{ route('login') }}"
class="font-semibold text-gray-600 hover:text-gray-900">Log
in</a>
@if (Route::has('register'))
<a href="{{ route('register') }}"
class="ml-4 font-semibold text-gray-600 hover:text-gray-900">Register</a>
@endif
@endauth
</div>
@endif
<h2 class="m-6 text-xl font-semibold text-gray-900 text-center">Laravel Library App</h2>
<table class="mx-auto">
<thead>
<tr>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
#
</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Title
</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Author
</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Release Year
</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Released Copies
</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Available Copies
</th>
@auth
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Actions
</th>
@endif
</tr>
</thead>
<tbody class="bg-white">
@foreach($books as $book)
<tr>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">{{ $loop->index + 1 }}</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
<div class="flex items-center">
<div class="text-sm font-medium leading-5 text-gray-900">
{{$book->title}}
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
<div class="text-sm leading-5 text-gray-500">{{$book->author}}</div>
</td>
<td
class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-no-wrap border-b border-gray-200">
{{$book->year}}
</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
@if($book->copies_in_circulation < 10)
<span
class="inline-flex px-2 text-xs font-semibold leading-5 text-red-800 bg-red-100 rounded-full">
{{$book->copies_in_circulation}}
</span>
@elseif($book->copies_in_circulation < 20)
<span
class="inline-flex px-2 text-xs font-semibold leading-5 text-orange-800 bg-orange-100 rounded-full">
{{$book->copies_in_circulation}}
</span>
@else
<span
class="inline-flex px-2 text-xs font-semibold leading-5 text-green-800 bg-green-100 rounded-full">
{{$book->copies_in_circulation}}
</span>
@endif
</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
@if($book->availableCopies() < 10)
<span
class="inline-flex px-2 text-xs font-semibold leading-5 text-red-800 bg-red-100 rounded-full">
{{$book->availableCopies()}}
</span>
@elseif($book->availableCopies() < 20)
<span
class="inline-flex px-2 text-xs font-semibold leading-5 text-orange-800 bg-orange-100 rounded-full">
{{$book->availableCopies()}}
</span>
@else
<span
class="inline-flex px-2 text-xs font-semibold leading-5 text-green-800 bg-green-100 rounded-full">
{{$book->availableCopies()}}
</span>
@endif
</td>
@auth
<td
class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-no-wrap border-b border-gray-200">
@if($book->canBeBorrowed())
<a href="{{ route('loans.create', ['book' => $book->id]) }}">Borrow book</a>
@else
<p class="text-red-600"> No copies available to borrow</p>
@endif
</td>
@endif
</tr>
@endforeach
</tbody>
</table>
</div>
</body>
</html>
This view will be used for listing books.
Now, create another folder named loans
within resources/views
. Next, create a new file index.blade.php
within the new folder and use the following content for it:
@php use Carbon\Carbon; @endphp
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Active Loans </h2>
</x-slot>
<div class="py-12">
<div class="shadow-sm sm:rounded-lg">
<div class="mx-auto sm:px-6 lg:px-8">
@if (session()->has('status'))
<div
class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative w-7/12 text-center mx-auto m-5"
role="alert">
<span class="block sm:inline">{{ session()->get('status') }}</span>
</div>
@endif
@if($loans->count() > 0)
<table class="mx-auto">
<thead>
<tr>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
#
</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Book
</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Number Borrowed
</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Return date
</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Actions
</th>
</tr>
</thead>
<tbody class="bg-white">
@foreach($loans as $loan)
<tr>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">{{ $loop->index + 1 }}</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
<div class="flex items-center">
<div class="text-sm font-medium leading-5 text-gray-900">
{{$loan->book->title}}
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
<div class="text-sm leading-5 text-gray-500">{{$loan->number_borrowed}}</div>
</td>
<td
class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-no-wrap border-b border-gray-200">
{{Carbon::parse($loan->return_date)->format('l jS F, Y')}}
</td>
<td
class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-no-wrap border-b border-gray-200">
<a href="{{ route('loans.terminate', ['loan' => $loan->id]) }}">Return book</a>
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<h1 class="m-6 text-xl font-semibold text-gray-900 text-center">You have no active loans</h1>
@endif
</div>
</div>
</div>
</x-app-layout>
Finally, create a new file named create.blade.php
in the resources/views/loans
folder and add the following code to it.
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Borrow Book') }} "{{$book->title}}"
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<form method="POST" action="{{ route('loans.store', ['book' => $book->id]) }}">
@csrf
<div class="mb-6">
<label class="block">
<span class="text-gray-700">How many copies would you like to borrow?</span>
<input type="number" name="number_borrowed" class="block w-full mt-1 rounded-md"
placeholder="How many copies would you like to borrow?"
value="{{old('number_borrowed')}}"/>
</label>
@error('number_borrowed')
<div class="text-sm text-red-600">{{ $message }}</div>
@enderror
</div>
<div class="mb-6">
<label class="block">
<span class="text-gray-700">Return date</span>
<input type="date" name="return_date" class="block w-full mt-1 rounded-md"
placeholder="" value="{{old('return_date')}}"/>
</label>
@error('return_date')
<div class="text-sm text-red-600">{{ $message }}</div>
@enderror
</div>
<x-primary-button type="submit">
Submit
</x-primary-button>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>
Update the routes
At this point, you have all the pieces of the puzzle in place. All that’s left is to update your route declaration so that requests are handled appropriately by the links provided in the views or when a controller redirects the user to a different part of the application.
Update routes/web.php
to match the following.
<?php
use App\Http\Controllers\BookController;
use App\Http\Controllers\LoanController;
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
Route::get('/', [BookController::class, 'index']);
Route::get('/dashboard', [BookController::class, 'index'])
->middleware(['auth', 'verified'])
->name('dashboard');
Route::middleware('auth')->group(function () {
Route::get('/books', [BookController::class, 'index'])->name('books.index');
Route::get('/loans', [LoanController::class, 'index'])->name('loans.index');
Route::get('/loans/{book}', [LoanController::class, 'create'])->name('loans.create');
Route::post('/loans/{book}', [LoanController::class, 'store'])->name('loans.store');
Route::get('/loans/terminate/{loan}', [LoanController::class, 'terminate'])->name('loans.terminate');
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');
});
require __DIR__.'/auth.php';
And that’s it! To run your application, open a terminal and run:
npm run dev
Open another terminal window and in this one, run:
php artisan serve
By default, the application is served on port 8000. Navigate to http://127.0.0.1:8000 to launch the application. To log in, you can either register or use the pre-saved user (email address: test@example.com
, password: password
). When you are logged in, you will be able to borrow books by clicking the Borrow book link and then filling out the form.
Conclusion
In this article, we have discussed how to build a PHP application using Laravel, Laravel Breeze for authentication, Eloquent as an ORM, Blade for templating, and a MySQL database hosted on PlanetScale. You started by creating a new database on PlanetScale, and then set up a Laravel project and connected it to the database. Using PHP Artisan and Eloquent, you set up your models and controllers. Finally, you updated the views to render the content returned by the controllers.
By following the steps outlined in this article, you should now have a functional Laravel 10 application that interacts with a MySQL database hosted on PlanetScale. You should also have a good understanding of how to build a web application using Laravel and MySQL. With this knowledge, you can continue to build more complex applications or customize this application to meet your specific needs. The entire codebase is available on GitHub. Happy Coding!