---
title: 'How to Build a REST API With Laravel: PHP Full Course'
source: 'https://youtube.com/watch?v=YGqCZjdgJJk'
video_id: 'YGqCZjdgJJk'
date: 2026-06-15
duration_sec: 6566
---

# How to Build a REST API With Laravel: PHP Full Course

> Source: [How to Build a REST API With Laravel: PHP Full Course](https://youtube.com/watch?v=YGqCZjdgJJk)

## Summary

This course teaches how to build a RESTful API using Laravel, covering everything from setup and data modeling to authentication with Laravel Sanctum. The instructor guides through creating models, migrations, controllers, and implementing features like filtering, sorting, bulk inserts, and token-based authorization.

### Key Points

- **Course Overview** [00:41] — The course covers building a RESTful API from scratch, including data models, seeding, GET requests, JSON transformation, filtering, validation, bulk inserts, and authentication with Laravel Sanctum.
- **Prerequisites and Setup** [01:44] — Requirements: PHP, HTTP server, database (MySQL or compatible), and Laravel. Options include Docker, XAMPP, or manual setup with Composer.
- **Creating Models and Migrations** [07:41] — Use Artisan to create models, migrations, factories, seeders, and controllers for Customer and Invoice with a one-to-many relationship.
- **API Versioning** [19:28] — Version APIs by segregating controllers into version folders (e.g., v1) and using route groups with prefixes to maintain backward compatibility.
- **Resource Classes for JSON Transformation** [26:22] — Use Laravel resources to transform model data into JSON, converting snake_case to camelCase and hiding fields like timestamps.
- **Filtering and Querying** [35:54] — Implement a reusable filter system using query string parameters with bracket notation for operators (e.g., `postalCode[gt]=30000`).
- **Including Related Data** [58:54] — Allow clients to include related data (e.g., invoices with customers) via query parameter `includeInvoices=true`, using conditional loading in resources.
- **Creating and Updating Resources** [65:42] — Use form request classes for validation, handle PUT (full replacement) and PATCH (partial update) requests, and transform camelCase input to snake_case for storage.
- **Bulk Insert** [82:56] — Implement a bulk store endpoint for invoices, validating an array of objects and using the `insert` method for efficient batch insertion.
- **Authentication with Laravel Sanctum** [93:46] — Sanctum provides token-based authentication. Tokens can have abilities (e.g., create, update) for authorization. If no abilities are specified, the token has full access.

### Conclusion

Laravel provides powerful tools like resources, form requests, and Sanctum to build secure, scalable RESTful APIs efficiently. The course demonstrates a practical approach to API development with reusable components and best practices.

## Transcript

[MUSIC]
Building a restful API can be a daunting
task because there are a lot of things
that you need to consider.
For one, you need a suitable framework
that is both powerful and flexible.
But it also needs to provide built in
tools to help you be more productive, so
that you can go from development to
deployment in a relatively short amount
of time.
And Laravel has got you covered.
Hi, I am Jeremy MCPeak with Tuts+.
And I invite you to join me so
that I can teach you how to build
a restful API using Laravel.
We'll start from scratch by
defining our data models and
seeding our database with test data.
We'll then dive right into developing
our API by handling basic GET requests.
And you'll learn how to transform the data
from the database into a consistent and
conventional JSON format.
You'll then learn how
to build a flexible and
reusable filter syntax so that end
users can easily query and filter data.
From there, you'll learn how to handle and
validate manipulative requests
such as POST, PUT and PATCH.
We'll also discuss how to implement
a bulk insert features so
that end users can quickly insert
multiple entities with a single request.
We'll then look at how to protect your
API endpoints with Laravel Sanctum.
And how you can use its token
capability feature to not only
authenticate requests but
also authorize them for specific tasks.
We have a lot of ground to cover,
so let's get started.
[MUSIC]
Of course, the first thing that we need to
do is make sure that you have everything
that you need to follow along.
And the list is relatively short, but
there's a variety of ways that you can
obtain and set all of these things up.
You need PHP, you need an HTTP server,
some kinda database,
preferably MySQL or something compatible.
And then you of course need Laravel.
Now, the one solution that's going to give
you everything is a Laravel Docker image.
If you're not familiar with Docker,
it is a virtualized environment.
It's not a virtual machine,
it is a virtual operating system, which I
know kinda sounds like I'm splitting hairs
but there is a technical difference.
Docker is for
creating what are called containers, so
that you can run applications regardless
of hardware and host operating system,
and that's all it is.
A virtual machine is for setting up CPU,
RAM, all of that other stuff.
But that's getting off into the weeds.
So if you go to laravel.com,
click on the documentation, and
then click on your first Laravel project.
And it's going to kind of walk you through
the process of setting all of that up.
Regardless of what operating system
you're on MacOS, Windows, or Linux,
this is an option for you.
However, for Windows,
it's a little bit more involved because
you have to install Windows subsystem for
Linux version 2 and enable that and get
all of that setup, but that is an option.
And when it comes to development
environments, I'm all for
virtualization because it completely
separates your development environment
from your main machine.
However, this particular machine
has some technical issues,
which prevent me from using Docker.
So instead, I'm going to use
something called XAMPP, X-A-M-P-P.
This is an all in one solution
that has everything but Laravel.
So this will give you PHP,
it gives you Apache,
MariaDB which is a MySQL Clone and Perl.
And one of the things that I really
liked about this is that there's
no configuration.
And so by downloading and installing
XAMPP, you get everything installed and
configured.
And that is a very nice thing because
if you install everything manually,
you have to configure everything manually.
But that's not it, you also get a nice
little control panel application, so
that you can turn these services on and
off at will.
And of course, using something like XAMPP
gives you a little bit more than just what
it says on the screen here.
There are other tools that are built
in that allow you to work with
a database like PHP,
MyAdmin and a few other tools.
So this is a very nice
little thing to have.
Now, if you're going to take this route or
if you are going to install and
configure everything manually,
then you will need to install Laravel,
and we do so using Composer.
The website is getcomposer.org, and
basically it is a very easy thing
to get set up on your system.
And then from there you just need
to go to the command line and
type this out,
composer global require laravel/installer.
And this is going to download and
install the Laravel installer, so
that you can create new projects on
the fly using the letter Laravel command.
And to do that, simply type laravel new,
and then the name of your project.
We'll call this laravel-api.
This will take a few moments to
finish creating our project.
But once that's done we can start
configuring our project to interact
with our database.
The installer creates a new directory
with the name of your project.
So the first thing you want to do
is cd into that new directory.
And we'll go ahead and fire up
the code editor, because we need to
modify the environment file that contains
the information about the database.
Now, in my case,
everything is already set up because
my system uses all of the defaults.
If you are using something like XAMPP or
some other kind of all in one type of
tool, then chances are you won't
need to change anything either.
If you manually set up your environment
then you might need to visit this file and
make some changes.
If you're using the Docker image then
I think everything is already done for
you, so you don't have to do anything.
Now, in my case,
I need to create this database.
And we can change the name of
the database if we want, but
I typically named the databases
the same as my project.
That just makes things a little bit
easier for my brain to process.
So you can name your database
whatever you want, but
do be sure that your connection
information is correct.
For me it is, so all I need to do
is go to my XAMPP control panel.
I need to start up the MySQL database,
which is actually a Maria database,
but I'm going to go to the admin.
And really, this is the only thing that
we need a administration tool to do.
You could use the command line for
the database if you wanted
to take that approach.
However, when it comes to working with
databases I prefer a graphical tool.
So I'm going to use what is given to
me by XAMPP, which is PHP MyAdmin.
And I am simply going to click on New,
here because this is for
creating a new database.
It shows you the databases that
are already here, many of these are built
in and system databases, so
we don't want to do anything there.
But as far as the database name I'm
going to use the name of the project
to create and
that is going to get this setup.
So that now whenever we use artisan
to make our migrations and stuff like
that it's going to connect to that
database and everything is going to work.
So in the next lesson,
we are going to do just that.
We are going to create our models and
our migrations for
the data that we are going to be working
with as we build this restful l API.
[MUSIC]
In this lesson we are going to do much
more than just create our models.
We are going to create the migrations,
the factories ,the cedars
as well as the controllers.
And of course,
that sounds like a whole lot of work but
some of that is going
to be done by artisan.
And now we are going to take a smaller
piece of a much larger project to where we
are just working with
a couple of types of data.
Such as customers and invoices that
we would issue for those customers.
So, let's get started by using artisan
to make our module called customer,
and we're going to use the all flag.
This is going to create well just about
everything that we need to work with this
type of data, and then we will do
the same thing for the invoice as well.
The relationship between these two types
of data is going to be a one to many,
one customer can have many invoices.
So, the first thing I want
to do is go ahead and
open up our models so
that we can make that relationship.
So, let's go to app,
modules, customer, and
we're going to add in our
HasMany relationships.
So, this will be just
a method called invoices,
where we will return the HasMany, and
we want to specify the invoice class.
And then for the invoice class we will
have just the inverse relationship.
So, really let's just copy this so
that we can paste it inside of invoice and
then make the necessary changes.
So, the inverse of
a HasMany is a belongsTo,
our class is of course going
to be the customer class, and
the name of our method
will be simply customer.
And that's all we're going to do
with the module class for now.
We're going to focus more
on the migrations and
everything else as far as
populating the database.
So, let's go to the Database folder,
open up Migrations,
we'll start with
the CreateCustomersTable migration.
In a real application there is
a ton of information related two
individual customer.
And I don't think we need
to have everything, but
I want a lot of data to just work with.
So, we're gonna have quite a few columns
here, the first is going to be the name of
the customer, and then we're gonna
follow that up with the customer type.
Now, the idea behind the type is that
the customer could be an individual,
or it could be a business.
So, this is just going to be a column that
will typically just have one character,
i for individual and a b for business.
And of course we would need some kind of
email in order to contact whoever it is
that we interact with.
And then it will be helpful to
have the address, city, state and
postal code, so let's go ahead and
get all of that set up.
And that's all that we are going to
include with our customers table.
Now of course, we could add a whole lot
more but this is going to be just fine.
So, now let's go to the invoice and
let's start defining this table.
Now, the very first thing that we need
other than the id is the foreign key for
the customer id.
But then it would also be useful to
have the amount of the invoice, and
let's just keep that as an integer.
Ideally, we would have more
precision here, but for
the most part,
as long as we have a numeric value,
that's really all that we care about
as far as our development is concerned.
And then we will have a status,
which is going to be a string, and
this is going to be somewhat similar
to the type of our customer,
in that it will typically
just contain one character.
Now, let's say that we could have a build,
meaning that we have gone ahead and
build the customer for this invoice.
We will have a paid signifying that
the customer paid the invoice and
then there will be a void because
sometimes mistakes are made, and
we need to void that invoice.
Now, it would be useful to have
some information about the status,
like if it is billed,
that we would have a build date, and
if it is paid,
then we would have a paid date.
Now, both of these of course
need to be date time,
so let's go ahead and specify that.
But an invoice might not be paid, in which
case pay date kind of needs to be no,
so let's mark this as mullable, and
then we will keep our timestamps.
And of course there's also a whole lot
more information that we can supply with
an invoice.
And ideally, we would have another table
that would list all of the line items of
that invoice but we're not going
to do that at least for right now.
But of course having tables isn't enough,
we need some data to work with,
so let's work on our factories.
So, let's open up our
CustomerFactory first, and
as far as the definition that
let's start with the name.
Now, names are going to be, well, they
don't have to necessarily be unique but
it doesn't make sense to
have a personal name for
a company or a company's name for
an individual.
So, really what we need to do first of
all, is get the type that the customer
that is going to be created is going to
be, and we can use a faker to do that.
So, we will use the random element method,
and
our array is going to have simply I and
b, of course for
individual and for business.
And then from there we can determine what
the name of this particular customer is
going to be.
So, if the type is equal to an I, then we
want to use the name method from faker,
because that is going to give
us an individual person's name.
Otherwise, we have a business,
in which case we want to use the company
method to give us a company name.
And then we can use that value for
the name, and then we have the type, so
we'll go ahead and set that value as well.
And then for the email,
it doesn't matter there, I mean, ideally,
the email would be somewhat related to the
company if this is some kind of business,
but we'll just use faker
to give us an email.
And then for address, city and state,
we will use fakers methods
to provide those values.
So, with our CustomerFactory defined,
let's hop on over to the InvoiceFactory,
and the first thing we will
set is the customer the id.
Now, one thing that we can do here is to
specify that this customer id is going to
come from the CustomerFactory, in which
case we need to import that module class.
And then for the amount,
all we need is some numeric value, and
it will be useful to have something that
is not zero [LAUGH] and not negative.
So, we're going to use
faker to give us a number
between let's say 100 and 20000.
Man, I would like to be able
to invoice for that much, and
then we need the status.
Now, the status can be one of three
values, it can be built, paid or void.
And really the next two fields,
the build, date, and
the paid date depend upon this status.
So, let's kind of do the same thing
where we are going to go ahead and
get the status.
Where we will use faker to once
again give us a randomElement
from an array that contains our statuses.
So, we have build, we have paid,
and then we have void.
And then for the build date,
really it doesn't matter
what it is because we will
always have a build date.
So, we will go ahead and
use a faker to give us a date, time,
value that is going to be
some time this decade.
And really the dates don't matter,
this is just test data to work with.
However, we do want something
that is a little bit realistic.
Now, one thing that we probably should do,
is get this build date and
then base the paid date off of
that if the status is indeed paid,
so we do need to check that here.
And if it is paid,
then we want to date, but
you know let's just do this,
once again we will call this decade.
Method, and if it is not paid,
then the value is going to be null.
And now that we have our factories,
we can use them to seed our database.
So let's close our factories,
let's go to the seeder's folder, and
let's open up CustomerSeeder and
InvoiceSeeder.
And really, the DatabaseSeeder as well,
because we will need that too.
And now that I think about it, I don't
really think we need the InvoiceSeeder,
because we can create everything
from our CustomerSeeder,
because this is exactly what we could do.
Let's, first of all,
use our factory method.
And let's say that we want at least 25
customers that have 10 invoices each.
I mean, we could be just lazy and
say that we have 100 customers,
they each have 10 invoices,
but let's not do that.
Let's have a different number of
invoices for some of our customers.
So we're gonna use our factory,
we're going to create 25 customers.
Each of them will have 25 invoices,
and we will create that.
So let's just copy and paste, so
that then we can have 100 customers
that each have 5 invoices,
then we can create another 100
customers that have 3 invoices.
And then finally, let's create some
customers that have no invoices,
and let's make this a small amount.
So five customers will not
have any invoices, and
they will just be in our system.
So with that in place,
we can go to our DatabaseSeeder, and
inside of the run method, we are going to
call the CustomerSeeder and there we go.
So that's now we should be able to
go back to the command line and
we will use artisan to migrate.
Let's use fresh, and
we also want to seed the database.
So if we typed everything correctly,
which, of course, we didn't,
so we cannot find the CustomerSeeder,
or rather we can't find customer
inside of the CustomerSeeder, which,
of course, we need to include.
So let's use App\Models\Customer.
Let's go back to the command line.
Let's run the fresh migration again.
Another error, and it looks like
that I used fake instead of faker.
What was that file that
was customer factory?
So let's open up the customer factory and
let's change fake to faker.
There we go.
Let's go back.
Let's run the seeder again, and
of course, we get something incorrect,
column not found,
billed date, and why is that?
So let's go to the migration and
I probably typed something incorrect.
That was inside of invoice.
So we have billed_dated,
and that's what I get for
typing and talking at the same time.
It's not that easy,
at least it's not for me.
So now, finally, hooray, everything works.
We have our stuff.
And so now that we have all
of our data in the database,
we can start developing our
API in the next lesson.
[MUSIC]
APIs are software.
They are a specialized kind of software.
But ultimately, they are software, we
write them, we release them, we fix bugs,
and then we decide to add new features,
so that we can release a new version.
However, our users are developers, they
are using our APIs in their applications.
And anything that we change
could break their applications.
So unlike conventional applications,
we need to version our APIs.
So that as we release new versions,
our old versions are still there,
they're functioning.
And developers who are using those old
versions can opt into the new features
in the new versions, or they can
continue to use the older versions.
So unfortunately, this means that
we have to not only keep track
of all of the different versions, but we
have to keep them inside of our project.
But that's just the nature
of developing an API.
So one of the easiest ways to do this is
to segregate the different pieces that we
need for different diversions, and the
controllers are really the most important,
because those are what
are handling the requests.
So this is what I typically do.
Inside of the controllers folder, I will
create a new folder simply called API.
The idea being that this is the folder
where all of the API controllers live.
This is especially useful if you have
a project that is both a typical web
application that is serving HTML, but
you also have an API that
you need to provide as well.
And then inside of the API folder, this is
where I would have the version folders.
So this is our first version,
so we're just gonna call it v1.
And then we will put inside of
there the controllers that we need,
such as the customer controller and
the invoice controller.
So let's go ahead and
let's move those there.
And of course, we need to do more than
just isolate these files, we need to
change their namespaces, otherwise we're
going to run into naming collisions.
So now, the namespaces for
the customer and
invoice controllers are app HTTP
controllers, API, and then v1.
And we need to make that change on both
of those, so let's just copy and paste.
But this is also going to change the
controller class that we are extending,
we need to import that controller.
So that's easy enough to do.
We'll just import App\ http\Controllers,
and then controller.
And of course,
we need that in both of those files.
So any controller that we
are going to add to our API will
need to make those changes.
And let's go ahead and
at least implement the customer
controllers index method, so that we would
then simply return all of the customers
that we have in the database.
Now, we could take this a step further and
we could also version our models,
because we could make
changes to the database.
However, those changes
are typically adding new columns,
as opposed to changing the columns
that are already there.
So from that aspect, versioning your
models is a little less common.
Actually it's a lot less common, but
you could take the same approach.
Now, of course, we need to set up
the routes for these controllers.
So let's go to the API
file inside of routes,
this is where we define the routes or
our APIs.
And we want to include
the version in the URL itself,
because every version
should have a unique URL.
And if you define a route inside of the
API file, then your URL begins with API.
So we would want then our version,
v1, or v2, or whatever,
and then our endpoints, such as customers,
or invoices, or things like that.
So one of the easiest ways to
do this is to use a group.
And there are several options
that we can apply here.
The first is going to be the prefix, and
we can simply set the prefix to
whatever version that we need.
So this is the first version.
So the prefix will be v1.
That is going to append to
the already existing prefix of API.
So we will have API/v1.
But we can also make our lives
a little bit easier by also specifying
the namespace, because all of our
controllers reside within the same
namespace, and that is simply
going to be App\http\Controllers.
Then Api, and then V1.
And then from there,
we just need to define our routes.
Now, we essentially have
resource controllers.
However, these are a little bit
different because they are for an API.
We don't really need to Create or
Edit endpoints.
So we can use the API resource method.
It essentially gives us the same thing,
but it omits the Create and
Edit endpoints, so
that then we can specify our endpoints.
So we have customers and we would want to
say that that is our customer controller.
And then we will essentially do
the same thing for our invoices,
except we of course want the URL to
be invoices and the controller is
invoice controller, and
I believe that's customer controller.
Okay, so with that in place,
we should be able to go to the browser and
at least hit the index route
on our customer controller.
So you of course need your
application up and running.
And I'm using Firefox because
when it receives a JSON response,
it will automatically parse it into
something that is halfway readable.
Well, it's more than halfway readable,
it's very readable.
So our URL is going to
be API/V1/customers,
and we should see the output,
and there we go.
As I said, this is very readable.
We can go through this, and
if we wanted to scroll through all what,
225 or 250 users, we could do that.
Now, as you'll notice,
this JSON payload looks pretty good,
except when you get to the fields like
postal code, created at, and updated at.
Instead of using underscores, we would
like to use camel case because that
is the convention for the JSON format,
and we want to follow the conventions
of whatever language or
format that we are using.
And so in the next lesson,
we are going to look at doing just that,
as well as filtering the results,
because we don't need to supply
everything such as the timestamps.
[MUSIC]
We ended the previous session
with code that actually works.
And there's a lot to say about code that
works because it's a lot better than code
that doesn't.
And all we are doing is returning all
of the records from the customers table
whenever we go to the customers endpoint.
And there are some things that I
want to change about that, like for
example, the postal_code property.
Now, for PHP and MySQL, that's perfectly
fine because that is the convention,
especially for MySQL columns.
However, now we are talking about JSON,
and
the convention in JSON
is to use camel case.
And anyone using our API is
going to expect camel case, so
we kind of need to address that.
Something else that I want to do is
remove the create and updated at fields.
Now, later on we're going to add
authentication, and I think for
authenticated users,
that would be perfectly fine that
we could provide that information.
But for just normal users
that are not authenticated,
they don't need to see that ,so I want
to hide that from a typical response.
So that's two things that I want to do,
and
we could approach those two things
in a variety of different ways.
But Laravel gives us something called
a resource that allows us to transform
an eloquent model into a JSON response,
and
we have complete control
over that transformation.
So let's create a resource,
we'll do so with Artisan.
The command is simply to make a resource,
and then we give it a name.
In this case, that's for our customer,
and we'll just call it CustomerResource.
But before you hit Enter,
we need to think about
versioning because this is something that
is going to directly affect our users.
This is the JSON response and
that can change over time, so
we want to version this as well.
So we can say V1/customer resource and
that's perfectly fine.
Artisan is going to create a new resource
class that is going to be inside of
the Resources folder that
just popped in there.
That is inside of App\Http\Resources, then
we have V1, and then CustomerResource.
And the namespace is automatically
set how we need it to as well.
So before we start changing this,
what I want to do is implement our
show method because that is going
to give us a much easier way of
showing how this is going to work.
So all we are going to do right
now is return to that customer.
So if we go to customers/1, that should
give us just a single customer, and great.
So we're going to modify this output
by using this resource class.
So all we need to do here is
return an array where the keys
are the property names that we
want in the JSON response, and
then we just provide the value for
those properties.
So for example, if all we wanted to
do is return the ID, we could do that
very easily by just specifying ID and
giving it the value of ID.
And as far as the syntax for
getting those values,
it's just like using our model class.
So we could save this and we could use
it inside of our customer controller.
So the first thing we need to
do is import this resource and
that is inside of App\Http\Resources\V1,
and then CustomerResource.
And then we are going to use this
class inside of our store method,
and we are going to create
a new instance of this and
we will pass in the customer object
as the argument to the constructor.
And with that simple change,
we can go back.
Let's refresh here and
we are going to see just the ID.
Now, the overall structure change,
we see that we have this data,
and that's part of using a resource.
Now, we could change that if we wanted,
but personally, I like that, so
we're going to leave that alone.
So of course,
we want to provide more information,
which means that we have to go
back to our customer resource and
we have to specify all of
the keys that we want to expose.
And for the most part,
it's going to be a straight up one to one.
So the ID is going to be the ID,
the name is going to be the name,
the type is going to be the type,
and so on and so forth.
But then we get to the postal code, and
this is where we'll use our camel case and
then we will specify the postal code from
our model as the value, and there we go.
So if we go back, we can refresh and
we see those values.
And if we look at postal code,
then we can see that that is camel case.
And of course, the timestamp
fields are not there because we
explicitly left them off inside
of our customer resource.
So with that in place,
let's go back to our customers.
And we are still going to get that same
response to where we see everything.
And we want to use our customer
resource to filter down each
individual object in the JSON.
Well, the way that we can do that
is by creating another resource,
except in this case,
we're going to call it CustomerCollection.
And this is still going to be a resource,
but it's going to be slightly different.
It is specifically for
working with a collection of things.
So if we open up CustomerCollection,
we see kind of the same thing.
And we can actually just
leave this alone so
that inside of our controller,
we of course need to import this.
But whenever we have our
customer collection,
we will use that almost exactly
like we used our customer resource.
We are going to new up
the CustomerCollection, and
then we will pass in the collection
that we want to transform,
which is going to be all of
the records from our customer model.
So if we go back to the browser,
let's refresh,
we are going to see the new JSON format.
Once again, we have this data that's
going to contain all of the data, but
the data itself has been transformed.
We can see the counter case for
postalCode.
And of course,
the timestamps are not there.
So how does all of that work then because
we did not specify the customer resource,
we just specified the customer collection?
Well, it's kind of all magic.
It's going to assume that there is
a customer resource, and it's going to use
that to transform each and every record so
that we get the payload that we want.
And one of the beautiful things about
this is that, especially for our index,
we don't want to return just all,
it would be nice if we would paginated.
So if we call paginate,
it's going to automatically provide
all of the information that our users
are going to need to retrieve
pages of that information.
So let's go back to the browser,
let's refresh, and
the overall structure is slightly
different, we still have our data.
But notice that we only have
15 items within the data.
Then we have the links that a client
can use to navigate to the first page,
the last page, and the next page.
And then there's some metadata about
the information in this particular page.
So of course, if we change this
to where we want page number 10,
then we are going to get the data for
page 10.
And we, of course, want to essentially
do the same thing for our invoices
even though we don't have anything set up
as far as the controller is concerned, but
we can go ahead and
create those resources.
So that first,
we will have the invoice resource, and
then we want an invoice collection.
And this one this is really important
because the invoice had the build date and
the paid date.
And does, of course, add underscores.
So if we open up the invoice resource,
we can change the return array for
the to array method,
specify the properties or
the fields that we want to expose and
how we want to expose them.
And then we just need to implement
that inside of the InvoiceController.
And of course, the first thing we
need to do is import those classes.
So let's copy what we have from
the customer controller, and
we will make the necessary changes.
And for the index, we will simply
return a new InvoiceCollection,
where we will paginate the data
from our invoice model.
And let's go ahead and
implement the show method as well.
So you'll simply return
a new InvoiceResource,
where we pass in the invoice,
and we are good to go there.
So let's just very briefly check that out,
make sure everything's working okay.
So if we go to Invoices,
we should see our data for
the invoices as well as the links and
metadata, we do.
If we visit the page,
let's look at the format,
we should see everything in camel case,
we do.
BilledDate and paidDate are camel case.
And of course, if we go to an individual
invoice, we will see the same format.
So now that we have the basics working,
we need to start focusing on
retrieving dynamic data such
as querying and sorting.
And we will start looking
at that in the next lesson.
[MUSIC]
One of the reasons why we have web
APIs is to provide access to data.
And if you're going to provide access to
data, you also need to provide the ability
to filter that data, because who wants to
try to find a needle in this haystack?
I don't.
Now, you could say that we
also need to provide search,
because search is
different from filtering.
However, over the many
years of running software,
I've come to find that I really don't
need searching when it comes to an API.
There's been a few times I needed
that functionality, but for
the most part, filtering works just fine.
So then the question becomes,
how are we going to do this?
Because if you do any kind
of research on API design,
you're going to find a ton of
different options and opinions.
But this is what I've settled on.
So first of all, we only want to filter
things that handled GET requests,
and only those GET requests
that return a collection.
That makes sense.
So for our case,
it's going to be customers and invoices.
We also want something that is going to be
reusable so that we don't have to write
a lot of code repeatedly for the same
functionality, because as our API grows,
we want to provide filtering
wherever it makes sense to.
But now comes time to the syntax of
the query string because there's
a lot of different opinions.
But what I've settled on for APIs written
with Laravel is something like this.
We'll first begin with whatever
fields that we want to filter on.
So let's say that we want to
filter based upon the postalCode.
And I know that this isn't
a great comparison, but
we only want the customers that
have a postal code greater than,
well, what amounts to be 30000.
The postal codes are strings.
They are typically five digits long, at
least in the US, but they can be longer.
And a lot of times, they contain a dash.
So yes, this is not a very good
comparison, but just run with me here
because Laravel will take this and
turn it into an array automatically.
There's no parsing that we have to do,
Laravel just does it automatically.
Whereas if we used something like colons
to where we would have postalCode,
then we would have the operator and
then the value.
We would have to parse that out.
And you might like to parse strings,
I don't.
So this is what we're going to go with.
And since this can be a fairly
complex type of thing,
I'm going to write the code that I,
not necessarily that I want to use but
what we're going to start with.
And then we will massage it into something
that will be much better to use.
So the first thing we are going
to do is add to the Request
parameter to our index because
we want to take this request and
pass it on to whatever is going
to build whatever we need for
filtering so
that we will need to create a new object.
We'll call this CustomerQuery.
And then we will have the items that
are going to be built from this class.
The filter will have
a method called transform,
to where we will pass in the request.
And then this transform method is going
to give us an array that we can just
straight up pass to the where method.
So this means that it's going to be
an array that's going to have the column,
followed by the operator, and
then whatever value is going to
be used for that comparison.
And this is actually going to be an array
of arrays that contain that information
because we want to provide the ability
to filter on multiple fields at
the same time.
So, this queryItems is going to have this
kind of structure so that we can just
straight up pass it to the where method,
and that's going to give us our filter.
Now, of course,
queryItems might be an empty array, so
we do need to check for that first.
And we can very easily do that with count.
So if that is 0, well, then we want to
continue doing what we are already doing,
just taking the data straight
up from calling paginate.
Otherwise, we essentially
want to do the same thing.
But instead of calling
paginate right after that.
We'll call where we will pass in the query
items and then we will call page eight.
So, that's the goal.
I mean, yes,
we want this to be much cleaner.
But at least in this lesson,
this is what we are going to implement.
So let's create a new
folder inside of app.
And let's just call it services.
And since this is something that
could be different based upon
the different version of the API,
we need to version it.
So, we will have V1.
And then inside of here
we will have a new file.
We'll call it customer query.
And let's start with the namespace.
That is going to be App Services V1.
And since we need access to the request,
we'll go ahead and
pull that in as well, that is inside
of illuminate HTTP and request.
And then we can just
write our customer query.
Now, the very first rule of handling
user input is to not trust user input.
And that is exactly what we
were working with anything
in the query string is user input.
So, really we want to make sure
that whatever is in the filter is
what we support.
And one of the best ways to do that is
to have something that we can opt into.
Kind of have the fillable inside of our
model classes which we haven't set up.
But if we wanted to we could come
in here with that fillable and
we could say that certain
columns are fillable.
It would be the same kind of thing, but
we would say that these fields
are safe fields or allowed fields.
I guess we can call it allowed pares.
And then,
this would of course be an array,
that would contain the fields
that we would allow to filter on.
But really we need a little bit more than
that because there are some fields that
makes sense for certain comparisons like
every one of them can be an equals but.
Yeah, when it comes to
the city a greater than or
less than doesn't make sense for city.
So, we also want just
the allowed operators as well.
Now, postal code I'm going to leave with
greater than if we have greater than
I guess we need less than as well,
but that's the idea.
So with that in place, I'm just going
to paste in all of the fields for
our customer.
So, everything will
allow equal postal code
includes the greater than,
and the less than.
But really, we also need something that's
going to transform our fields into
the database columns.
And for the most part,
we don't need to worry about that.
But our postal code is
one of those that we do.
So, it would make sense to
have a column map as well.
And for this particular case,
our column map is just going to
have that one for the postal code.
So, the key could be the field
from the query string.
And then the value could be the actual
column name that we would need to provide
the database.
And that is postal code or postal column.
Then we also need some way to transform
the operators that we're using from
the query string into the operators
that eloquent, is going to need.
And we can do that with another
map called an operator map.
So, I'm just going to paste that in
because here's quite a bit there.
Now this is just the bare bones there
are many different operators that
a SQL database supports, such as
the in operator and the like operator.
So, if we wanted to supply that
functionality we can do that as well.
So, then it just comes down to
the actual functionality of
transforming the request query string into
an array that we can pass on to eloquent.
So, here we are going
to accept the request.
And we are going to build an array
which I'm just going to call eloQuery.
The idea being that this is going to be
the array that we are going to pass to
eloquent.
So, we will create that then
we will return that and
then we just need to build that array.
So, the first thing we're going to
do is iterate over our safe parms,
because it makes more sense
to iterate over what is safe.
As opposed to iterate what is
inside of the query string.
So, we want to iterate
over our safe parms as.
And we need both the field name,
which I'm going to call parm.
And the set of operators that are allowed
for that field, because we need to
check not just the parameters, but
the operators that are being used.
And then, we will get whatever is in the
query string for this particular field.
So, we'll do that by passing in the parm,
which is our field name.
And it's entirely possible that we
don't have anything to work with.
In which case, there's nothing else to do,
so, we'll just simply continue.
Otherwise, we have some actual
stuff that we need to work with.
So, let's first of all get the column
that we are going to use in our query.
So, we will use our column map and
we're going to pass in the field name.
Now it's possible that we don't
actually have a value there.
And in fact in this particular case
most of the time that will be the case.
So, we do need to provide a default value
which is of course going to be the field
name itself.
But then we need to filter the operators.
So, we're going to use another for
each and
we need to check if we
have that operators.
So, we'll use this set to do that and if
this operator is allowed for this field.
Well, then we want to add
an element to our eloQuery.
And it needs to be in the format of
an array, where the first element is
the column followed by the actual
operator that we want to use.
So, we will use the operator map here,
we'll pass in the operator that we need.
And then we need the value that's
going to be used for that comparison.
And that is from our query.
And that should do it.
We return the eloQuery array,
and we're good to go.
So, all we really need to do then is,
import this class inside
of our controller and
everything should be ready to go.
I guess we'll find out Okay, so
we'll have the customer query there.
We are creating that filter.
We're transforming it.
We are checking if we have
any valid filter options.
Everything looks good.
So, let's go to the browser.
First of all refresh make sure
that we didn't break anything.
And of course,
I did not use the use statement.
That's kinda important there,
so, let's refresh.
And now we have an undefined
variable query item, that's because
it's supposed to be query items,
let's refresh, there we go.
Okay, so our basic query,
which is not a query works just fine.
Let's do some filtering.
So, let's first of all filter
based upon the postal code
is going to be greater than
let's see what we have here,
we have something that's eight
to let's do greater than 30,000.
That is five digits long.
So the item like Nicolas Lachman, there.
An aging or in brackets those
should not be in the result set.
So, let's hit enter let's
see what happens and
something just blows up operator map and
that's an array that's not a method.
So, let's change that and
then we'll go back.
Let's see is this the third time, I don't
remember, whatever time this was the term
because there we go, we can see that
we no longer have those entries.
But let's do this, if we look at the type,
we have several there's a business,
there's a business, there's a business,
let's do a filter just for an individual.
So we'll add that as
another query parameter to
where the type is going to be equal to i.
And all of the business entities should
not be there, everything is an i.
So it looks like our filter
is working just fine.
So there's a few things that
you might be screaming at me.
First of all, we don't have the ability
to do an or filter, and that is correct.
Unfortunately the query string in the URL
lends itself to only allow and queries.
So if you wanted to include an or there
would need to be some way that we do that
through the syntax of the query string,
and
frankly I don't have
a good way of doing that.
But that's also just the nature
of URLs and query strings,
it is implicitly in and
query, so there's that.
Second of all,
is this is not reusable at all, and
you're correct I mean,
we could copy and paste.
But really what we want to do is
break this out into a base class,
so that we could then use that base class
to implement a much smaller class for
the invoices, and
we will do that in the next lesson.
[MUSIC]
In the previous lesson we created a class
so that we can filter the results
of our customers endpoint,
and it works rather well,
if I do say so myself.
And so we want to replicate the same
functionality not just for the invoices
endpoint but for any other endpoint
that we would implement in the future.
So really we want to extract all of the
key functionality from this customer query
class into a base class so that we can
use that base class whenever we need to.
But first I wanna make a few changes,
instead of calling the services I want
to rename this folder to filters.
Because we could make the argument
that this is a kind of service class.
But I think we would be better served if
we keep all of the filters together and
separate from everything else.
Especially as we implement more
versions and things like that.
So there's a few changes I want to make
such as the namespace and set of services,
we'll have filters, let's change the name
of this class as well to customers filter.
That way we know that this is a filter for
customers,
let's also change the name of the file,
so that is apparent.
And so the idea is that this will extend,
we can call this API filter and
ideally API filter will not be versioned.
We'll start with that and if later on we
decide that we need to version this base
class then we can do that.
So let's create that file
outside of our v1 folder, so
this is going to be
directly inside of filters.
And really, we can start by copying and
pasting everything from
the customers filter class.
So let's do that and
then let's just make a few modifications.
The first is, of course,
going to be the namespace,
we don't need the v1 there,
the name of the classes API filter.
And as far as all of our arrays are
concerned, we just need them to be empty.
So we will delete
everything inside of them,
you could make the argument
that operator map,
should have at least these base operators
but we're gonna make this empty as well.
Again, if we decide that we want to
implement that we can implement that.
And there we go, we have our API filter.
Now, we do need to import this
inside of our customers filter,
so let's go ahead and do just that.
And then we will need to modify
our customers controller.
So let's do that before
we implement the invoice
because after all this is
going to be very quick.
So let's open up that file we'll
change the services to filters,
the customer query to customers filter and
then we will use that class.
Now, one thing about this class,
we could make the argument that we really
don't want to new up the constructor so
that we could use the filter.
And it would be nice to have
a façade instead, however,
there's a lot involved
with creating a facade.
In fact,
we would have to then weigh what is more
valuable as far as our time is concerned.
Setting up a facade so
that we can not new up this constructor or
to just new up the constructor.
Of course,
we could also use the service container so
that it would be injected,
but there once again,
we are dealing with a lot of setup for
not really a big payoff.
I think I'm just going to
leave this as is, it works and
there's a lot to say about code that works
as we can see here, so we're good to go.
Now, we just need to essentially
recreate customers filter, but
it will be for the invoices.
So let's just copy and paste that file,
let's rename the copy to invoices filter.
And it's been a while since we have looked
at the fields that we have for invoices.
So after we change the name of this class,
let's open up the migration for
the invoices table so
that we can take a look at those and
there are several.
So let's just copy this so that we can
have a reference to it inside of our
invoices filter and
then we will just need to set up the map.
And we will start with the column map,
and we have three,
we have customer ID,
build date and paid date.
So let's just make the necessary changes,
and then let's set up the safe parms,
which I think we called allowed parms or
something like that.
I probably paste it in the wrong variable
name, but, well that's gonna be fine.
So we have our customer ID,
we'll have amount,
we'll have status billed date and
then paid date.
We do not need anything else.
Now, as far as the operators
are concerned, equals is going to work for
all of them.
However, I would say that amount needs,
well, all of the operators,
because it makes sense to want to be able
to filter whether or not the amount is
less than or greater than, or less than or
equal, or greater than or equal.
And really, we could set the same
thing for the billed date,
as well as the paid date.
And it would also be useful to have a not
equal operator for like the status.
Because it would be useful to say
that we want to find the invoices
that are not paid, or that are not void,
or that are not billed.
So we will call that ne, and
we of course, need to set up a map for
that, which is going to be the traditional
PHP not equal, and that will work.
So now,
all we have to do is reference the,
we need to delete the transform method,
don't we?
So that means we need to do the same
thing inside of customers filters, so
let's delete that.
And then we will open up
the invoices controller so
that we can use our filter there.
And really, we can just copy and
paste from our customer controller, so
let's do that.
And then we will make the necessary
changes because we do need to implement
the index method.
In fact, let's just copy all of the index
methods so that we don't have To
manually type in the request parameter,
but of course the name of our class
is invoices filter, and then we will
just paste that wherever we need.
So let's hop on over to the browser and
let's duplicate this tab so
that we can have both the customers and
the invoices up.
And we will do just
the straight up invoices, so
that we can first of all see all
of the results, there we go.
Let's say that we want to find
the invoices that are not paid.
So that means we are going to filter based
upon the status that is not equal to P.
And the reason why I picked that is
because the invoice with the ID of one was
paid and
we can see that that is no longer there.
So our filter for
our invoices is working just fine.
Now there's one other thing notice that
the links that are provided do not contain
the query string.
So if we navigate to any one of these,
then we're going to lose the filter.
And that's easy enough to fix.
What we will need to do is first of all,
do the same thing that we were doing
before, by calling layer passing in
the filter and then calling paginate.
But what we're going to pass to
the invoice collection in this case is
going to be invoices.
And we are going to
call the append method,
because we want to
append the query string.
So we will get that from the request.
We will call query and that should work.
Let's go back.
Let's refresh,
make sure that that does indeed work.
It does.
So now we just need to replicate
that on the customer controller.
So let's do the same thing where we
are going to first of all get our
paginated results we'll just store
that in a variable called customers.
And then whenever we use our
customer collection we will use
our customers call appends to where
we will append the query data.
So let's go back to the browser,
let's go to our other tab.
Let's make sure that that is
indeed the case that yes we do not
see the query string included.
So by refreshing that should be there,
and it is.
So, we are now successfully filtering both
our customers and invoices endpoints.
In the next lesson, we are going to
implement the ability to include
related data such as including
invoices with our customers.
[MUSIC]
Sometimes it makes sense
to include related data.
Like for example, I think it makes
sense to include the invoices,
whenever we request our customers.
But we don't want to do that for
every request.
We want the client or the user to
specify when they want that information.
So we could do something very simple
by having this include invoices
query parameter.
If it's true,
then of course we include it.
If it's false, or if it's not there, then
we of course, don't include the invoices.
And there are many different ways that we
can approach this type of functionality.
But I want to caution you,
it's very easy to get excited
about implementing features.
But it's very easy to over
engineer your application and
develop features that well
they just aren't used.
So if my experience has taught me anything
it is that build an application or
build software with basic functionality,
and let the users tell you
what features they want.
Because you could spend a lot of time
implementing features that aren't used,
and then you end up with a bunch of code
that's just cluttering up your code base
and it's just a mess.
So in this lesson we are going
to take that approach.
We're going to build the basic
functionality of including the invoices or
not including the invoices.
So let's go to our controller and
let's start implementing that feature.
So we will just call
this include invoices and
we're going to check the query string for
that.
Now we are already branching our code
once based upon our query items which
really that should be called filter
items that makes a lot more sense.
So if we don't have any filter items then
of course we just start a call paginate.
If we do have query items then we pass
that to where and then we call paginate.
But here's a quick little tip
when it comes to queering with
eloquent if you pass in an empty array,
there is literally nothing to execute
as far as where is concerned.
So in this particular case if we
passed an empty array to where,
it's almost like we aren't calling where
at all and we are just calling paginate.
So we could simplify this so that we don't
have to check if there's any filter items,
instead we'll just pass
the filter items on to where.
If there are any filter items that great
if not, that's perfectly fine as well.
So this is going to give us the ability
to essentially build our query and
include the invoices if we need to.
So, the first thing that
we will do is simply
call where we will pass
in the filter items.
And then we will check if we have
the include invoices in which case we will
simply include them.
We do so by calling with and
then specifying our relationship invoices.
And then when it comes to
calling paginate we will do that
down here whenever we pass our
collection to the customer collection.
So we will have customers and
then paginate and
then the pins and there we go.
So that should be great.
Let's go to the browser.
Let's add true to this.
And let's execute.
And column not found,
column zero where clause.
And that's the problem on line 26,
we are wrapping filter items
with an array that's not right.
So there we go we refresh and
we don't see the invoices.
Well the reason is very simple
because remember that the payload that
has been sent in the response is
actually being controlled by our
customer collection resource class.
And really, by extension,
the customer resource, and
we don't list the invoice here at all.
So of course,
we want to include the invoices, but
we only want to do that when
we have that data to display.
And to makes it very easy to do that.
We are simply going to
use our invoice resource.
And we're going to call
the collection method now,
this is going to essentially use
the invoice collection that we wrote
which is of course going to
use the invoice resource.
But what we're going to pass through this
collection method is indeed the invoices,
but we only want to do that when
the invoices have been loaded.
So we will use this when loaded method and
that's the magic behind this working.
So if we go back to the browser and
refresh we are now going to see
our customers with the invoices.
If we take out that query parameter,
then we only see the customers.
So now we have that functionality and
really, we want to implement that for
the individual customer as well.
So let's duplicate our tab and
let's just go to the customer
with an ID of nine because
that's what's on top.
And there we go.
Of course, we haven't implemented
this code, but let's go ahead and
let's include invoices here.
We'll set that to true.
And we will essentially do the same thing.
So let's copy this line where we
are going to include invoices.
And let's go down to our show, but this is
going to be a little bit different because
we aren't building a query here,
we already have our customer.
So, we are going to do this.
We'll first of all check if we
want to include the invoices.
And if so, we are still going to
return this new customer resource.
But then, we're going to call this load
missing method on our customer object.
We want to load the missing invoices,
and that's going to make that work.
And then of course if we don't
want to include invoices,
we just return the customer as normal,
and everything is fine.
And since we've already
changed our customer resource,
we are going to see this work so that we
have an undefined variable of request.
And that's because we don't have request.
So, let's use the request function here.
Let's go back, let's refresh, and
here we have the customer information.
And then we see his invoices.
If we take out that query parameter, then
of course we will only see the customer.
So of course the magic is
inside of our resource class.
By using this when loaded method,
we are able to conditionally
include the related information for
our resource.
[MUSIC]
We are finally to the point where
we can start manipulating our data.
So in the next lesson, we are going to
be looking at creating editing, and
updating our resources.
Now, before we begin,
it would be useful to have a client
that we can use to issue HTTP requests.
Now, yes, we do have a browser and
we could technically do that.
We could pull up the console, and
then issue requests using JavaScript, but
that's a little bit
more work I want to do.
Now I'm going to be using postman,
which is cross platform.
However, there are many
other options available.
I'm a Windows guy, so when it comes to
doing anything with HTTP, I typically
go for Fiddler, because Fiddler Classic
is free, and it's Windows only.
However, there is a Fiddler
version that is cross platform,
it's just not free, unfortunately.
But of course, feel free to use whatever
tool that you are used to using.
All that matters is that you are able
to issue whatever type of request
that you need, and
provide the data for that request.
So with that out of the way, we are going
to start with our customer controller.
And remember that this
is an API controller.
So there are a couple of methods
that aren't set up as far
as routes are concerned, and
we no longer need them, such as
the create method, and the edit method.
So let's just go ahead and
get rid of those.
And one of the great things about using
Laravel to develop a restful API is that,
we can use all of the tools
that are available for
typical Laravel applications,
such as request classes.
So, we can create a class called
store customer request, and
we can put all of the validation
rules inside of that class.
And Laravel is going to automatically
handle all of that for us, so that at
the end of the day, all we really need
to do is return a new customer resource.
Because that's typically
what we would do for
a post request,
we would return the new entity, and
then we would just need to call
the create method on our customer model,
pass in all of the data from the request,
and we would be good to go.
Now of course, there's a few things
that we need to do to make this work.
For example, we need to specify
the fillable array inside of our customer
model, so let's do that.
Now always be careful when you specify
the fields that you want to be fillable.
Really think about it
because it could have some
pretty damaging effects if
you're not very careful.
In the case of our customer model,
it's okay if all of the fields
are fillable, I'm fine with that.
So, let's just say that we'll close that,
and we'll be good to go.
Then we're gonna hop on over
to the command line, and
we're going to use artisan to make a new
request called store customer request.
Now this does need to be versioned because
this is data coming from the client,
and that means it's data for
a specific version.
And of course artisan is
going to create that folder,
create the request class inside of that
folder, so that we don't have to do that.
So, inside of app HTTP requests v1,
we have this new store customer request.
Now the first thing I'm going to do,
is flip this authorized to true.
Now, of course, yes, we only want
authorized users to create a customer.
However, we don't have
that capability yet.
So, we need to be able to test this first.
And then we want to specify
our validation rules.
And I'm going to paste this in
because it's quite a bit of typing.
But for the most part,
it's very straightforward.
All of the fields are required,
because we have no optional fields
when it comes to creating a customer.
But the type and
the email are a little bit special.
The type is special,
because we only have two types, really,
we have an individual in the business.
So I'm using the n-rule here, so
that if the client provides anything other
than i or b,
then it's going to be rejected.
And then the email field is,
well, it needs to be an email.
So we're using that validator as well.
Now because I'm using rule here,
we do need to import that class,
that is inside of illuminate,
validation, and then, rule.
But, we're not done here,
because remember postal code.
Now it's fine that it's camel case for
our rules,
because that is the information
that is coming from the client.
However, we need to be able to transform
that into postal underscore code,
so that we can properly
store that in the database.
And one of the ways that we could do that,
is by using the prepare for
validation method.
Because this allows us to merge in
other values such as postal_code.
So we're going to use the merge method.
The array that we're going to pass
to it will have a single key called,
guess what, postal_code.
And we're going to say that it's
going to have the value of postal
code that came from the request.
So, there we go, that's going to allow us
to get that information into the database.
And so now we just need to import
this class into our controller.
So, let's go over there,
we will add the use statement for
our namespace, and
then the store customer request class.
Now we've already used that in
the store method, so we are good to go.
We can hop on over to postman, and
we should be able to create a new user.
Let's first of all make a request for
just the customers endpoint, just so
that we can make sure that's
everything is going to work, okay?
It should, because we really
didn't change all of that much.
There we go, we see our data.
And now we're going to change
this to a post request,
which means that we
need to include a body.
I'm going to choose the raw option,
and choose JSON.
And I'm going to paste
in a valid JSON payload.
However, before we actually store
this new entity in the database,
I want to test some of
the validation rules.
Like for example,
I want to make sure that we cannot
create a customer with a type of z.
So, whenever we send this,
what we're actually going to see is
the result of viewing, the welcome view.
That is the markup that we're seeing
if we look at the preview here,
that's exactly what we see.
And at first, we might think that we did
something wrong, and we kind of did.
But remember that the basic functionality
of Laravel is request validation,
is to redirect.
Usually, it is to the Create or
the Edit action.
And we don't have those
inside of our controller, so
it's just going back to index.
Now, we could change our code.
However, really what we need to do is
address the headers that we made with
the request.
Because if we look on down at the except
here, now our content type was correct.
It was set to application/json
because I chose JSON as the data type.
However, the accept is
set to accept everything.
And we don't want that,
we want to only accept JSON.
So we're going to disable the accept
that it added to that request.
We're going to add it back.
But in this case, we will say that
we only want application/json.
So that whenever we submit that same
request, we are going to get a response
back that says that the given data was
invalid and the selected type is invalid.
So that makes sense.
We do not allow any other type than i or
b.
But let's also remove the name,
and let's see what happens there.
We will essentially see the same thing.
It will still say that
the given data was invalid, but
then it says that the name
field is required.
So our validation appears to be working.
We could test all of the fields that
we can just assume that everything
is going to work just fine.
So with a valid payload, let's submit
that request, and this should work.
Although, we get a response back that
the column not found postalCode.
Where was postalCode used?
I know that we use it inside of
the customer request to merge in
the postalCode.
So, that means we need to look at
the model, and I bet that's where it is.
And yep, that's it.
So we are going to change that to be
the actual column name as it should have
been to begin with.
We can go back to Postman, and
we can resubmit this request.
And we should get back
the entity that was created.
And there it is.
So we now have a new customer,
the id is 231.
So if we wanted to hop on over to Firefox,
and let's make a request for
that, we should see that customer,
and we do.
But let's say that's, we made a mistake,
we need to change some
of this information.
Well, that is where we can issue a put or
a patch, and
we will see how to do
that in the next lesson.
[MUSIC]
In a typical web application, we provide
the ability to edit an entity or
a resource by loading
the data into a form so
that the user can make whatever
changes that they need to.
They submit the form,
and then we, of course,
save those changes into the database.
But a RESTful API is very different.
We actually have two types of edits.
The first uses a put request,
which is to replace an entire entity.
So this is the customer that we
created in the previous lesson.
It has an id of 231.
So if we wanted to edit this
customer with a put request,
our request would need to
include everything, the name,
the type, the email address,
and everything.
We cannot leave anything out because we
need to provide the values that are then
going to replace the values for name,
type, email address, city, and so on.
Now, the values could be the same
thing as what's in the database, but
the idea's that we have to
supply all of the values.
Now, the second type of
edit uses a patch request,
which is more along the lines of
what we typically think an edit is.
So that if we only wanted to change the
name, type, and email, then we only supply
the name, type, and email, and those are
the only properties that would be updated.
And that's all well and good, except that
when it comes to Laravel applications,
we have just a single update method,
and this method handles both put and
patch requests.
So the request class that we are going
to create needs to handle both of
those types of requests, which sounds
difficult, but it's not at all.
So let's first of all make that request,
we'll call it UpdateCustomerRequest.
Now, we do need to version this.
And we're going to start by copying and
pasting pretty much all of the code
from our StoreCustomerRequest.
Because the rules that we have defined
are the same rules that we need for
a put request.
Because remember a put request requires
us to include all of the properties,
so we need all of the rules.
So let's copy everything from authorize to
rules to even prepareForValidation
because we need all of them.
And then we are going to simply
paste those into our update request.
And then inside of the rules method,
what we're going to do is
check the HTTP method, and
we do that by using the method method.
This gives us the HTTP method
that is in all uppercase.
So if you want to compare with PUT or
PATCH or whatever,
you need to be sure to
use all uppercase here.
So for a PUT request, we want to perform
the same validation that we did for
creating our customer.
And that's all well and good.
Now, from here, we could assume that
if we are inside of this class,
then chances are pretty good
that we are in an update.
And so we could just go ahead and use
an else here because the idea being that
it's either going to be a put or a patch,
and I think it's safe to do that.
So we are going to leave it like this, and
we are going to essentially
use the same rules.
However, we are going to add the sometimes
rule to each one of these properties.
The idea is that if name is not there,
then it's not going to be validated.
However, if name is in the payload,
then it is sometimes going to be there.
And when it is, then we'll validate it
according to the rules that are specified.
So, this is really it.
All we've to do is use the sometimes
rule for each one of these properties,
and that is going to allow us
to validate for a patch request.
Everything else is going to be the same.
So we just need to import this class,
and then we, of course,
want to use this class for
the type hint for our update method.
And there we go.
So that now all we need to do is use the
customer that we get, we will call update,
and then we will simply pass in
all the data from the request.
And that's going to work just fine.
So let's head over to Postman,
let's first of all issue a request for
the ID of 231 because that is what we need
to make our patch and put requests for.
We want to send those requests to
whatever resource that we want to update.
So here's our customer that we created,
id 231.
And let's start with a put request.
So I'm going to paste in the payload
that we use from the previous lesson.
But let's test everything.
Let's change all of these values.
So this is going to be me2@tutsplus.com.
We'll change the address to 4321 wherever,
some town can become some city.
Let's change Texas to something else,
like Oklahoma, O-K-L-A-H-O-M-A, Oklahoma.
And this one,
we will change the postalCode.
Let's just reverse it, 54321.
Let's submit,
we need to change that to a PUT request.
We need to make sure that we have
the appropriate headers because we don't
want to recreate the issue of being
redirected because validation failed.
It shouldn't fail, but still,
the accept is application/json.
And let's just send this.
So that we shouldn't get anything
back from the API because a put or
a patch request typically
doesn't give us anything.
However, we need to import
that rule class, don't we?
So we need to copy that from
the store customer request class.
We will paste that into update.
And then we will go back,
let's reissue this request.
And everything should be fine then,
and it is, we're good there.
However, let's go to Firefox and
let's take a look at our user,
all of the values should be updated
because we changed them all.
So that's great, but let's say okay,
we made a couple of mistakes.
It is an individual so we need to change
the type, we don't wanna live in Oklahoma,
so let's change that back to Texas,
everything else we can leave the same.
So, I guess we should change the name.
So, let's do that.
We'll change the type back to I, and
then the state is gonna be Texas.
So, we're changing only three things.
We can get rid of everything else
actually, or we could include them if we
wanted, but that defeats the purpose
of testing a patch request.
So, let's change the type two patch,
let's submit that request.
Once again, we should not get
anything back from the server.
However, the status should be 200,
but it's not, it's 500.
And we ran into an issue,
integrity constraint violation
column postal code cannot be no.
That kind of makes sense because
if we don't have postal code,
then we don't need to change anything
about the data that we are working with.
So, let's just do this.
If we have postal code,
then we will merge in the new array
that has the postal_code key,
otherwise we won't do anything,
that should fix that.
So let's go back, let's resubmit this
request, that error should go away, and
in fact, we shouldn't get anything back,
looks great.
So, let's go to Firefox.
Let's request the data once again,
and we can see that the name changed,
the type changed.
The email stayed the same as
did the address, the city,
the state changed, and
the postal code is the same.
And so now clients can update
data in our database using our
API by issuing both a put or
a patch request.
[MUSIC]
Sometimes we want to provide
the functiontionality for
inserting records in bulk, and
not every API needs to provide that.
However, I think it makes
a whole lot of sense for hours,
especially if it comes to invoices.
In fact, I have worked on accounting
systems where the person scanning and
indexing those invoices,
will do so in batches.
And then when they were done with
that batch, they would submit it, and
then it would be saved in the database,
that's really an oversimplification.
But, that's the general idea.
Now, because we are using
resource controllers,
there isn't a built in method for
handling a bulk type of request.
So we're gonna have to write
our own which is just fine,
there's nothing wrong with that.
Now, we're going to call it bulk store,
the idea being that I'm using
the verbiage that's already here.
So bulk store means that we're going
to create multiple records in bulk and
we will have a customer request
class to validate this information
because this is very important.
Because now that we are accepting
data in bulk we want to be very
sure that before we even try to
store something in the database
that we have our data
exactly how we want it.
But we're going to start using just
this generic request object and
then after we get everything set
up we will use our custom class.
Let's go ahead and set up our route for
this new endpoint which I
guess we could just call bulk.
So it'll be invoices/bulk, and then we
will specify where to route that request,
that is of course our Invoice controller,
and the bulk store.
Now, we could use the artisan
to create art request class but
I think we will be better served by just
taking one of our existing requests and
copying and pasting and modifying.
I'm going to use the store customer
request because it is primarily designed
for storing stuff.
And let's call this bulk
store invoice request.
That's rather a mouthful, but
it is very descriptive as to what it is.
Now, before we start defining our rules,
I guess we need to talk about what
the payload is going to look like.
So it's basically just going to be
an array that has objects representing
the invoices, so there will be
customer ID, and so on and so forth.
If we have multiple which I would
hope we would have multiple,
then it would just be another
element in that array.
So, one thing to keep in mind
is that we are going to validate
the individual objects inside of
an array and I'm using JSON terms here.
So we're gonna have a JSON array that
contains a bunch of JSON objects, and
Laravel gives us the ability
to handle the validation for
an array of multiple objects.
And it's going to look
something like this, so
that we want to validate the customer
ID property on each object.
Now remember that we
are working with an array, so
we're going to pre-pend them with
an asterix and then the dot.
And we will essentially do the same thing
for all of the other properties, so
let's just copy and paste a few times.
I think we have five fields,
we have the customer ID, the amount,
then we have the status,
then the build date, and the paid date.
Now the customer ID is required,
and ideally,
we would also check to see if the actual
customer existed in the database.
Because, If it doesn't,
then we're gonna run into problems.
But we do want to make sure that we have
in the integer value because the ID of
the customer is going to be an integer.
Then we want to validate the amount
which is of course required but
then it's going to be a numeric value.
The status will be required, but
it's going to be using an in rule,
where we need to change these values so
that we have B for
build, P for paid, and V for void.
Let's also go ahead and
have the lowercase versions of those and
then we have the build date,
so this is required.
And we also want to say that we
need the date in a specific format,
which is going to be year, month, day,
space, hours, minutes, and seconds.
And we essentially want the same
date format for the pay date.
However pay date is not required,
in fact it can be known, so
we also want to say that this can be
knowable and that is going to be it.
So whenever you want to
validate an array of objects,
the keys in your rules need to reflect
whatever data structure that you're
going to have since all
we have is an array.
We're going to use a star dot,
and then the property name.
Now, if our data structure looked
like this where we had data and
then that would be an array where we
would have the individual objects.
Then it would be simply data dot star dot,
and then the property name.
Now as far as the prepare for validation,
this is where you're taking the values for
the camel cased properties and assigning
them to the underscore properties.
We'll still need to do that but it's going
to be a little bit more involved here.
That essentially means we need to iterate
over each element in each array and
make those same changes.
So what I'm going to do is,
Build a brand new array we're just
going to call it data and
we're going to use a foreach loop and
we're going to turn the input that
we get into an actual PHP array so
that we can use it in a foreach and
then we are basically going to
add the underscore fields here.
So that will be customer ID.
Now remember that this is prepare for
validation.
So validation hasn't occurred yet and it's
very possible that the payload does not
include the customer ID in which case
this would actually cause an error.
So, what we want to do then is if there
is not a customer ID in the payload,
well of course that's invalid,
but we need to get to the point to
where we can validate the payload.
So, we're going to go ahead and
assign customer ID as null and
if validation fails, then that's okay.
Nothing's lost or gained and essentially
the same thing for the other fields.
And then once we set these new keys,
we are going to add this child
array into our data array, so
that after the loop is done then we will
merge in that new data that we have and
that is going to give us
exactly what we want.
Except that we're going to run into
an issue instead of our invoice controller
because now we're going to have an array
that contains not just the actual column
names in the database but we're also
going to have these Camelcase-keys.
And the method that we are going
to use inside of our bulk store
which is going to be insert.
It's going to assume that everything
that we're going to pass to it is
going to have a column in the database
that means that it's going to try
to insert something into a column
called billedDate all in Camelcase.
And that's a problem we will
run into an error there so
now we need to take those out.
So I'm going to do this,
I'm going to create a new array and
I'm going to turn the request
information into a collection so
that it makes it a little bit easier to
work with because collections have the map
function so that we can essentially
create a new array that can change
just the columns that we need and
that's going to look a lot like this.
So that to inside of our closure, we
are going to use the array accept helper.
And we basically want to say,
we want all of the columns
except these customer ID billedDate and
then paidDate.
Everything else is fine.
We want to store that in the data base and
we will be good to go.
So that once all that is done then we
will use invoice we will call insert and
we will pass in bulk but we can only pass
in an array we can't pass in a collection
so we will call it toArray method and
that will get us what we want.
Now there are a few things that we need to
import here first is that array façade and
I don't know if we call it array or not.
I don't want to call it r because
that sounds kind of piratey but
we'll just call it Arr, how's that?
We need that,
we also need to import the request class.
And we need to use the class name for
the type hint on our bulk store method.
So let's do that there.
That's very important because otherwise
we would not get our validation.
So now we just need to test this out.
Now I've gone ahead and
pulled up the record for
the first customer,
we have 10 invoices and
we're going to add two more so
we will have 12 after this is done.
So let's hop on over to postman and let's
make a post request to invoices/bulk.
And there is our payload.
Let's make sure that we have
the application JSON accept header we
still do.
And let's, first of all,
make this invalid.
So I'm going to remove the customer ID
on one of these and we will submit.
If everything was typed correctly, then
we should get a validation message back
saying that, the first customer
ID is not there and there it is.
The zero dot customer
ID field is required.
So I find that this is very helpful
because whoever is submitting
this will know exactly where to look for
the customer ID.
Let's put the customer ID back and
then let's submit this again.
And we shouldn't see anything in the
response because we didn't return anything
because I don't really know what we
should return for a bulk insert.
But let's go to Firefox.
Let's refresh the page.
We should have 2 more.
And we do.
And so that's how you can
add the functionality for
bulk inserting records to your API.
It's not as straightforward
as other types of requests.
But thankfully Laravel gives us
the tools that we need to validate and
modify the input payload
to make it all work.
[MUSIC]
Laravel seven introduced
a new feature called Sanctum.
It is a token authentication
scheme primarily for API's and
single page applications.
And that makes a whole lot of sense
because single page applications typically
use an API in order to work with and
manipulate data.
However, token authentication has always
kind of been a thorn in the side of
developers because a lot of authentication
packages want you to use a OAuth,
and that just complicates
the heck out of everything.
So thankfully, Sanctum doesn't.
It is an easy to use, easy to implement
token authentication scheme that
ties very nicely into Laravel's
default authentication scheme.
And Sanctum is installed by default and
the later versions of Laravel.
You can check by going
to composer.JSON and
inside of there require section
there should be Laravel Sanctum.
Now if it's not there then you will
of course need to install it you can
use composer to require
Laravel Sanctum and
then after that is installed,
you would want to publish it and
you would use artisans vendor
publish command to do that.
You would specify the provider of
Laravel Sanctum Sanctum service provider.
And then once that's done, you will want
to run the migration that is created
because that is going to add
a table to your database called
Personal Access Tokens and it ties
in very nicely with the users table.
Now of course we haven't done anything
with our users but a typical application
would have some kind of interface so
that the admin can manage those users or
at least the users could sign in and
assign themselves tokens.
Now we aren't going to implement
any of that because that would take
quite a bit of time instead we are going
to have a URL called it setup.
And we are basically going to
check to see if a user exists and
if it doesn't then we are going to create
that user so that then we can assign
some tokens and of course if the user does
exist then nothing is going to happen.
So I've already written the code
that is going to create that user
the next thing we need to do is sign
in with that newly created user.
So let's do that with our credentials, and
then we will retrieve the user because
we need to use its create token method,
and it would be a lot easier if we
just assign that to a variable.
So we're going to create three tokens,
one is going to be the admin token,
the idea being that this is going to
be the token that admins would use.
That give them all of the capabilities
that they would need.
So we're going to use our user, and
we're going to call createToken.
Then we're going to
give this token a name.
Now, the name really doesn't mean much.
It's just simply a name.
And we could leave it like this and
create a token.
However, we can assign
capabilities to a token so
that whenever we receive
a request that includes a token,
we could check to see if
there are any capabilities.
And we can determine whether or
not whoever issued that request has
authorization to perform whatever
action that they were trying,
such as creating something or
editing something.
So since this is an admin, we want to
essentially be able to do everything.
So we can create, we can update,
and we can delete.
Now, I know we haven't implemented
deletion because, well,
it's just kind of straightforward.
You receive a delete request and
you delete the record.
There's really nothing else there.
And then we would want one for
a user that would be able to update.
So let's call this updateToken.
We'll call the name updateToken.
And they will be able to create an update,
but we don't want them to be able
to delete, and that will be fine.
And then finally,
we will have our basicToken,
which is just going to
be read only access.
We don't want to provide any
functionality except being able to read.
And we want to sign any capabilities for
this token.
Now, whenever we create these tokens,
we, of course,
are going to get the plain text of that.
However, what is stored in the database
is actually the hash of that key.
So if you want somebody to be able to
use their token, you need to output
the token because this is the only
time that we can get that plain text.
So we're going to return
an array with three things.
We'll have the adminToken, and
we are going to get the plainTextToken,
that is the actual token that
will be used with the request.
Then we essentially want
the others as well.
So we'll call the second one update.
We'll call the third one basic.
And of course, we want to use
the update and basicTokens there.
So that's going to give us the tokens that
we will then want to include within our
requests.
And we will talk about how we can do that.
But first,
I want to open up the routes for our API.
And I want to go ahead and
I want to use the Sanctum middleware to
protect all of the endpoints in this API.
Because I think that makes good sense,
we don't want just anyone to
be able to hit our API and
get whatever data that they need.
They need to have a token.
So we are going to use the Sanctum
middleware for our authentication scheme.
And that'll be good there.
So let's close out of our routing files.
And let's go to the browser because we
want to make a request to that setup URL.
So let's duplicate one of these tabs,
let's go to setup, and
then we will get our API keys.
And we will, of course, copy these so
that we can use them later on.
So let's look at the raw data that's copy,
and
let's put them inside of a notepad,
just so that we have them.
Now, when it comes to using these tokens,
we want to use the value.
We, of course, have these names,
admin, basic, and update, but
[100:01] we want to use the values,
starting with the numeric value,
[100:05] followed by the pipe, and
then the token itself.
[100:08] And we are going to include this
with the authorization header.
[100:12] So let's first of all try to make
a request, just a simple GET request for
[100:17] the customer's endpoint.
[100:19] And we should not be able to because
the API is now protected with Sanctum,
[100:24] and we can see that we
were redirected to login.
[100:28] I can't find login.
[100:29] So we are good there as far as
our authentication is concerned.
[100:34] So we want to go to postman because
this is going to allow us to add
[100:38] the authorization header.
[100:40] Let's do essentially the same
thing to where we are going
[100:44] to try to retrieve the customers.
[100:46] And of course, if we send this,
we are going to get the same thing.
[100:50] It will be an error that says,
unauthenticated.
[100:54] So let's go to the Authorization tab here.
[100:56] We want to set the type to Bearer Token.
[101:00] And then we will provide
the value of that token.
[101:02] So let's start with the basicToken.
[101:06] We're going to copy that value.
[101:07] We will put it inside of here.
[101:09] And then we will submit
this request once again.
[101:13] And we should see the results
of making requests for
[101:17] our customers, and there they are.
[101:20] In the next lesson, you will learn how to
use Sanctum's capabilities feature so that
[101:26] you can either allow or deny authorization
for certain types of requests.
[101:32] [MUSIC]
[101:35] Laravel Sanctum has a fantastic
feature that allows us to assign
[101:39] capabilities to a token.
[101:41] This is a great way of authorizing
whether or not a client can do something
[101:46] that they are requesting to do,
such as creating, updating, or deleting.
[101:52] So we, of course,
want to provide that functionality for
[101:57] our create and update requests.
[101:59] So let's open up those files.
[102:02] And we want to open up all three of them,
the BulkInsert, the StoreCustomer,
[102:07] and the UpdateCustomer.
[102:08] And let's start with the StoreCustomer.
[102:11] And basically,
what we want to do is, first of all,
[102:15] get the user because the user object
is going to have a method that will
[102:20] allow us to determine whether or
not the token can do something.
[102:25] But we need to check to see
if we have a user first.
[102:28] Because if we don't and if we tried to
call this method, then our code blows up.
[102:33] So the first thing that we want to do
after getting the user is checking if it
[102:37] is not null.
[102:38] Because if it's not, then we want to
proceed and use the token can method,
[102:43] which is going to check to see if our
token can do a certain capability.
[102:49] In this case, we are creating something.
[102:52] So we want to see if the token or
[102:54] the request that has
the provided token can create.
[102:58] And if it can't, then of course,
we return false, otherwise,
[103:02] it's going to return true.
[103:03] So there,
we have protected this request, and
[103:07] we essentially want to do the same
thing in the other requests.
[103:12] So let's go to
the BulkStoreinvoiceRequest,
[103:15] let's use the same code for the authorize.
[103:18] Now, if we wanted to break this down even
more, we could do something like this
[103:23] to where we could have a capability
of customer:create or invoice:create.
[103:29] So that only certain people
could create invoices,
[103:32] certain other people could
only create customers.
[103:35] I didn't want to go that granular,
we are just creating, updating, and so on.
[103:41] So this is going to be just fine here.
[103:43] Now, for the UpdateCustomerRequest,
this is going to check for
[103:47] a different capability called update.
[103:50] Once again, we could have done
customer:update, or invoice:update, or
[103:55] something along those lines,
but you get the idea.
[103:58] So now these requests should be
protected so that our basicToken should
[104:03] only have read access, it should
not be able to manipulate anything.
[104:08] So let's test that out,
I have the token for the basic user here.
[104:14] Let's change the request to a POST.
[104:17] We are going to try to
create a new customer.
[104:21] So I have a payload ready to go for that.
[104:24] And let's add that payload to the body.
[104:28] And whenever we submit this, we should see
that we are not authorized to do that.
[104:33] However, we get the response back
indicating that it was created.
[104:38] Now, the question becomes, how?
[104:40] Because whenever we
created this basic-token,
[104:44] we did not specify any
capabilities whatsoever.
[104:47] Let's take a look at the database.
[104:49] We want to go into the personal
access tokens, and
[104:52] we will see the three
tokens that we created.
[104:55] Now, we have the admin,
the update, and the basic-tokens.
[104:59] We have the hashed tokens.
[105:02] Notice that these are not the actual
values of the tokens themselves.
[105:06] Remember that I said that we only see
those whenever we create the tokens.
[105:11] So you need to remember that.
[105:14] But look at the abilities.
[105:17] So admin has the abilities of create,
update, and delete, that makes sense.
[105:21] Update has create and update, make sense.
[105:26] Basic-token has an asterisk.
[105:28] And I find this kind of interesting,
because if you're going to go through
[105:33] the process of setting up abilities for
some tokens, but you don't for others.
[105:38] Then you would expect that,
like in our case,
[105:41] the basic-token wouldn't
have any ability whatsoever.
[105:44] That's not the case.
[105:45] If you don't specify an ability
whenever you create a token,
[105:49] you are essentially creating
a token that has all abilities.
[105:54] So the way to fix this or
[105:55] the way to avoid this is to
assign abilities to your tokens.
[106:00] But of course, we want to fix this so
[106:03] that we don't run into this issue
with these tokens that we have.
[106:07] So we can simply edit this record.
[106:10] And we can change that so that instead of
an asterisk, we have a string of none,
[106:17] or basic, or whatever we would
want to refer to an ability.
[106:22] I think none kind of makes sense,
so we're going to run with that.
[106:27] So now if we go back to postman, and
we try to run this same request,
[106:32] we are going to see that the basic-token
will not be able to do so.
[106:37] This action is unauthorized.
[106:39] So that was perfect.
[106:41] But let's test one of our other tokens.
[106:44] Let's test the admin-token.
[106:46] And all we need to do
is change the value for
[106:49] the authorization header token so
that we will use the admin-token.
[106:54] We will send this request.
[106:56] We should get the response back,
indicating that the customer was created,
[107:01] and it was.
[107:02] And just for the sake of completeness,
let's test the update.
[107:06] And instead of creating a new customer,
[107:09] let's edit this one that we just created,
so that was with an ID of 234.
[107:15] Let's use a patch, and
let's change the payload so
[107:19] that all we're going to do is change
the name from John Doe to Jim Doe.
[107:25] And if we issue that request, we should
get nothing back, which is what we wanted.
[107:31] But now, let us request that customer,
and there we go.
[107:37] We can see that the update was successful,
that we have Jim Doe now.
[107:41] So protecting requests
is very straightforward.
[107:44] When you use Sanctum, your user
object has a method called tokenCan.
[107:49] And if you have assigned
abilities to token,
[107:52] then you can check if that token
can do that certain capability.
[107:56] Just remember that if you don't
assign any abilities to a token,
[108:01] then that token can do anything.
[108:03] So if you plan on using that feature,
[108:06] define an ability that is the most
restricted that you want.
[108:11] [MUSIC]
[108:15] Laravel has the tools that
we need to build usable and
[108:19] scalable applications,
including RESTful APIs.
[108:22] In fact, Laravel does a lot of
the hard stuff for us, making it so
[108:26] much more enjoyable to build software.
[108:29] For example, you learned how using
Laravel form request classes,
[108:33] not only work for
incoming JSON payloads, but
[108:36] their built-in validation
capability saves us so much time.
[108:40] And Sanctum,
[LAUGH] its authentication and
[108:43] authorization features makes it so
easy to protect our resources.
[108:49] It's these types of features
that help us write better and
[108:52] more feature-filled software.
[108:54] Laravel has a huge community.
[108:56] So if you get stuck on anything,
not just writing an API, don't be shy,
[109:01] hit the web and ask questions.
[109:03] We are all happy to help.
[109:05] Thank you so much for
watching this course.
[109:07] Please feel free to contact me
through Twitter or the Tuts+ forums,
[109:11] if you have any questions.
[109:12] From all of us here at Tuts+,
[109:16] thank you, and I will see you next time.
[109:21] [MUSIC]
