Thật tuyệt tạo dữ liệu mẫu với Database Seeding (ok)
https://www.toptal.com/laravel/restful-laravel-api-tutorial
Ví dụ đã hoàn thành :)
C:\xampp\htdocs\blog\app\Article.php
C:\xampp\htdocs\blog\database\migrations\2020_07_25_154811_create_articles_table.php
C:\xampp\htdocs\blog\database\seeds\ArticlesTableSeeder.php
Laravel API Tutorial: How to Build and Test a RESTful API
With the rise of mobile development and JavaScript frameworks, using a RESTful API is the best option to build a single interface between your data and your client.
Laravel is a PHP framework developed with PHP developer productivity in mind. Written and maintained by Taylor Otwell, the framework is very opinionated and strives to save developer time by favoring convention over configuration. The framework also aims to evolve with the web and has already incorporated several new features and ideas in the web development world—such as job queues, API authentication out of the box, real-time communication, and much more.
In this tutorial, we’ll explore the ways you can build—and test—a robust API using Laravel with authentication. We’ll be using Laravel 5.4, and all of the code is available for reference on GitHub.
RESTful APIs
First, we need to understand what exactly is considered a RESTful API. REST stands for REpresentational State Transfer and is an architectural style for network communication between applications, which relies on a stateless protocol (usually HTTP) for interaction.
HTTP Verbs Represent Actions
In RESTful APIs, we use the HTTP verbs as actions, and the endpoints are the resources acted upon. We’ll be using the HTTP verbs for their semantic meaning:
GET
: retrieve resourcesPOST
: create resourcesPUT
: update resourcesDELETE
: delete resources
Update Action: PUT vs. POST
RESTful APIs are a matter of much debate and there are plenty of opinions out there on whether is best to update with POST
, PATCH
, or PUT
, or if the create action is best left to the PUT
verb. In this article we’ll be using PUT
for the update action, as according to the HTTP RFC, PUT
means to create/update a resource at a specific location. Another requirement for the PUT
verb is idempotence, which in this case basically means you can send that request 1, 2 or 1000 times and the result will be the same: one updated resource in the database.
Resources
Resources will be the targets of the actions, in our case Articles and Users, and they have their own endpoints:
/articles
/users
In this laravel api tutorial, the resources will have a 1:1 representation on our data models, but that is not a requirement. You can have resources represented in more than one data model (or not represented at all in the database) and models completely off limits for the user. In the end, you get to decide how to architect resources and models in a way that is fitting to your application.
A Note on Consistency
The greatest advantage of using a set of conventions such as REST is that your API will be much easier to consume and develop around. Some endpoints are pretty straightforward and, as a result, your API will be much more easier to use and maintain as opposed to having endpoints such as GET /get_article?id_article=12
and POST /delete_article?number=40
. I’ve built terrible APIs like that in the past and I still hate myself for it.
However, there will be cases where it will be hard to map to a Create/Retrieve/Update/Delete schema. Remember that the URLs should not contain verbs and that resources are not necessarily rows in a table. Another thing to keep in mind is that you don’t have to implement every action for every resource.
Setting Up a Laravel Web Service Project
As with all modern PHP frameworks, we’ll need Composer to install and handle our dependencies. After you follow the download instructions (and add to your path environment variable), install Laravel using the command:
After the installation finishes, you can scaffold a new application like this:
For the above command, you need to have ~/composer/vendor/bin
in your $PATH
. If you don’t want to deal with that, you can also create a new project using Composer:
With Laravel installed, you should be able to start the server and test if everything is working:
When you open localhost:8000
on your browser, you should see this sample page.
Migrations and Models
Before actually writing your first migration, make sure you have a database created for this app and add its credentials to the .env
file located in the root of the project.
You can also use Homestead, a Vagrant box specially crafted for Laravel, but that is a bit out of the scope of this article. If you’d like to know more, refer to the Homestead documentation.
Let’s get started with our first model and migration—the Article. The article should have a title and a body field, as well as a creation date. Laravel provides several commands through Artisan—Laravel’s command line tool—that help us by generating files and putting them in the correct folders. To create the Article model, we can run:
The -m
option is short for --migration
and it tells Artisan to create one for our model. Here’s the generated migration:
Let’s dissect this for a second:
The
up()
anddown()
methods will be run when we migrate and rollback respectively;$table->increments('id')
sets up an auto incrementing integer with the nameid
;$table->timestamps()
will set up the timestamps for us—created_at
andupdated_at
, but don't worry about setting a default, Laravel takes care of updating these fields when needed.And finally,
Schema::dropIfExists()
will, of course, drop the table if it exists.
With that out of the way, let’s add two lines to our up()
method:
The string()
method creates a VARCHAR
equivalent column while text()
creates a TEXT
equivalent. With that done, let’s go ahead and migrate:
You can also use the
--step
option here, and it will separate each migration into its own batch so that you can roll them back individually if needed.
Laravel out of the box comes with two migrations, create_users_table
and create_password_resets_table
. We won’t be using the password_resets
table, but having the users
table ready for us will be helpful.
Now let’s go back to our model and add those attributes to the $fillable
field so that we can use them in our Article::create
and Article::update
models:
Fields inside the
$fillable
property can be mass assigned using Eloquent’screate()
andupdate()
methods. You can also use the$guarded
property, to allow all but a few properties.
Database Seeding
Database seeding is the process of filling up our database with dummy data that we can use to test it. Laravel comes with Faker, a great library for generating just the correct format of dummy data for us. So let’s create our first seeder:
The seeders will be located in the /database/seeds
directory. Here’s how it looks like after we set it up to create a few articles:
So let’s run the seed command:
Let’s repeat the process to create a Users seeder:
We can make it easier by adding our seeders to the main DatabaseSeeder
class inside the database/seeds
folder:
This way, we can simply run $ php artisan db:seed
and it will run all the called classes in the run()
method.
Routes and Controllers
Let’s create the basic endpoints for our application: create, retrieve the list, retrieve a single one, update, and delete. On the routes/api.php
file, we can simply do this:
The routes inside api.php
will be prefixed with /api/
and the API throttling middleware will be automatically applied to these routes (if you want to remove the prefix you can edit the RouteServiceProvider
class on /app/Providers/RouteServiceProvider.php
).
Now let’s move this code to its own Controller:
ArticleController.php:
The routes/api.php
file:
We can improve the endpoints by using implicit route model binding. This way, Laravel will inject the Article
instance in our methods and automatically return a 404 if it isn’t found. We’ll have to make changes on the routes file and on the controller:
A Note on HTTP Status Codes and the Response Format
We’ve also added the response()->json()
call to our endpoints. This lets us explicitly return JSON data as well as send an HTTP code that can be parsed by the client. The most common codes you’ll be returning will be:
200
: OK. The standard success code and default option.201
: Object created. Useful for thestore
actions.204
: No content. When an action was executed successfully, but there is no content to return.206
: Partial content. Useful when you have to return a paginated list of resources.400
: Bad request. The standard option for requests that fail to pass validation.401
: Unauthorized. The user needs to be authenticated.403
: Forbidden. The user is authenticated, but does not have the permissions to perform an action.404
: Not found. This will be returned automatically by Laravel when the resource is not found.500
: Internal server error. Ideally you're not going to be explicitly returning this, but if something unexpected breaks, this is what your user is going to receive.503
: Service unavailable. Pretty self explanatory, but also another code that is not going to be returned explicitly by the application.
Sending a Correct 404 Response
If you tried to fetch a non-existent resource, you’ll be thrown an exception and you’ll receive the whole stacktrace, like this:
We can fix that by editing our exception handler class, located in app/Exceptions/Handler.php
, to return a JSON response:
Here’s an example of the return:
If you’re using Laravel to serve other pages, you have to edit the code to work with the Accept
header, otherwise 404 errors from regular requests will return a JSON as well.
In this case, the API requests will need the header Accept: application/json
.
Authentication
There are many ways to implement API Authentication in Laravel (one of them being Passport, a great way to implement OAuth2), but in this article, we’ll take a very simplified approach.
To get started, we’ll need to add an api_token
field to the users
table:
And then implement the migration:
After that, just run the migration using:
Creating the Register Endpoint
We’ll make use of the RegisterController
(in the Auth
folder) to return the correct response upon registration. Laravel comes with authentication out of the box, but we still need to tweak it a bit to return the response we want.
The controller makes use of the trait RegistersUsers
to implement the registration. Here’s how it works:
We just need to implement the registered()
method in our RegisterController
. The method receives the $request
and the $user
, so that’s really all we want. Here’s how the method should look like inside the controller:
And we can link it on the routes file:
In the section above, we used a method on the User model to generate the token. This is useful so that we only have a single way of generating the tokens. Add the following method to your User model:
And that’s it. The user is now registered and thanks to Laravel’s validation and out of the box authentication, the name
, email
, password
, and password_confirmation
fields are required, and the feedback is handled automatically. Checkout the validator()
method inside the RegisterController
to see how the rules are implemented.
Here’s what we get when we hit that endpoint:
Creating a Login Endpoint
Just like the registration endpoint, we can edit the LoginController
(in the Auth
folder) to support our API authentication. The login
method of the AuthenticatesUsers
trait can be overridden to support our API:
And we can link it on the routes file:
Now, assuming the seeders have been run, here’s what we get when we send a POST
request to that route:
To send the token in a request, you can do it by sending an attribute api_token
in the payload or as a bearer token in the request headers in the form of Authorization: Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw
.
Logging Out
With our current strategy, if the token is wrong or missing, the user should receive an unauthenticated response (which we’ll implement in the next section). So for a simple logout endpoint, we’ll send in the token and it will be removed on the database.
routes/api.php
:
Auth\LoginController.php
:
Using this strategy, whatever token the user has will be invalid, and the API will deny access (using middlewares, as explained in the next section). This needs to be coordinated with the front-end to avoid the user remaining logged without having access to any content.
Using Middlewares to Restrict Access
With the api_token
created, we can toggle the authentication middleware in the routes file:
We can access the current user using the $request->user()
method or through the Auth facade
And we get a result like this:
This is because we need to edit the current unauthenticated
method on our Handler class. The current version returns a JSON only if the request has the Accept: application/json
header, so let’s change it:
With that fixed, we can go back to the article endpoints to wrap them in the auth:api
middleware. We can do that by using route groups:
This way we don’t have to set the middleware for each of the routes. It doesn’t save a lot of time right now, but as the project grows it helps to keep the routes DRY.
Testing Our Endpoints
Laravel includes integration with PHPUnit out of the box with a phpunit.xml
already set up. The framework also provides us with several helpers and extra assertions that makes our lives much easier, especially for testing APIs.
There are a number of external tools you can use to test your API; however, testing inside Laravel is a much better alternative—we can have all the benefits of testing an API structure and results while retaining full control of the database. For the list endpoint, for example, we could run a couple of factories and assert the response contains those resources.
To get started, we’ll need to tweak a few settings to use an in-memory SQLite database. Using that will make our tests run lightning fast, but the trade-off is that some migration commands (constraints, for example) will not work properly in that particular setup. I advise moving away from SQLite in testing when you start getting migration errors or if you prefer a stronger set of tests instead of performant runs.
We’ll also run the migrations before each test. This setup will allow us to build the database for each test and then destroy it, avoiding any type of dependency between tests.
In our config/database.php
file, we’ll need to set up the database
field in the sqlite
configuration to :memory:
:
Then enable SQLite in phpunit.xml
by adding the environment variable DB_CONNECTION
:
With that out of the way, all that’s left is configuring our base TestCase
class to use migrations and seed the database before each test. To do so, we need to add the DatabaseMigrations
trait, and then add an Artisan
call on our setUp()
method. Here’s the class after the changes:
One last thing that I like to do is to add the test command to composer.json
:
The test command will be available like this:
Setting Up Factories for Our Tests
Factories will allow us to quickly create objects with the right data for testing. They’re located in the database/factories
folder. Laravel comes out of the box with a factory for the User
class, so let’s add one for the Article
class:
The Faker library is already injected to help us create the correct format of random data for our models.
Our First Tests
We can use Laravel’s assert methods to easily hit an endpoint and evaluate its response. Let’s create our first test, the login test, using the following command:
And here is our test:
These methods test a couple of simple cases. The json()
method hits the endpoint and the other asserts are pretty self explanatory. One detail about assertJson()
: this method converts the response into an array searches for the argument, so the order is important. You can chain multiple assertJson()
calls in that case.
Now, let’s create the register endpoint test and write a couple for that endpoint:
And lastly, the logout endpoint:
It’s important to note that, during testing, the Laravel application is not instantiated again on a new request. Which means that when we hit the authentication middleware, it saves the current user inside the
TokenGuard
instance to avoid hitting the database again. A wise choice, however—in this case, it means we have to split the logout test into two, to avoid any issues with the previously cached user.
Testing the Article endpoints is straightforward as well:
Next Steps
That’s all there is to it. There’s definitely room for improvement—you can implement OAuth2 with the Passport package, integrate a pagination and transformation layer (I recommend Fractal), the list goes on—but I wanted to go through the basics of creating and testing an API in Laravel with no external packages.
Laravel development has certainly improved my experience with PHP and the ease of testing with it has solidified my interest in the framework. It’s not perfect, but it’s flexible enough to let you work around its issues.
If you’re designing a public API, check out 5 Golden Rules for Great Web API Design.Related: Full User Authentication and Access Control – A Laravel Passport Tutorial, Pt. 1
UNDERSTANDING THE BASICS
What is Laravel?
Laravel is an opinionated PHP framework. It abstracts away the minutiae of building a web application to facilitate productivity, maintenance, and forward compatibility.
What is REST?
REpresentational State Transfer (REST) and RESTful web services represent a style of network communication between applications to transfer application states through a stateless protocol (such as HTTP).
What is the difference between JSON and XML?
JSON and XML are textual data formats. JSON (JavaScript Object Notation) uses JavaScript syntax to represent data and make it parseable while XML (eXtensible Markup Language) uses markup tagging and nesting to achieve the same thing.
What is Composer?
André Castelo
Developer
ABOUT THE AUTHOR
André Castelo is a software engineer focusing in front-end development, with years of experience building Javascript applications using React or Vue. He has also worked in several full-stack projects using PHP and Python, leveraging tools such as Laravel and Django to deliver quality products.Hire André
Last updated