• Introduction to API’s
• HTTP Methods & Responses
• JSON Api Best Practices
• How to setup Laravel Passport
• How to create access tokens
• How to build resources
• CRUD REST API with Laravel
Setup the repository
git clone git@github.com:zorenleolumosbog/api-laravel-passport.git
cd bookstore
composer install
cp .env.example .env
php artisan key:generate
php artisan cache:clear && php artisan config:clear
php artisan serve
We are going to pull in data from the database so make sure that you have your database setup
mysql;
create database bookstore_collections;
exit;
Setup your database credentials in the .env file
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=cars
DB_USERNAME={USERNAME}
DB_PASSWORD={PASSWORD}
Migrate the tables
php artisan migrate
I struggled a lot in the beginning when I started working with API’s and that was mainly because I couldn’t get a clear understand of what an API is. I always try to explain it with the following example
Imagine a restaurant where visitors (obviously) sit at a table and eat their food. The food is prepared by chefs in the kitchen. You can see the menu as an API, because visitors pick their food, and the kitchen will cook that.
The API part makes it possible for a frontend developer to request a specific task or resource from the backend. When you want to order your food, you choose between different endpoints. When you order, you connect the backend of an application which will then send back some data.
When working with API’s, you’ll be hearing the term REST
a lot. Rest stands for:
• R & E – Representational
• S – State
• T – Transfer
Rest is an architectural style that is used for communication between a server and a client.
• Rest uses HTTP, so Hypertext Transfer Protocol as a base communication style.
• REST data will be sent in either XML or JSON because they are both “Readable by humans”.
Available HTTP methods on a resource
Verb | Path | Action | Route Name | Description |
---|---|---|---|---|
GET | /books | index | books.index | Get all books |
GET | /books/create | create | books.create | Get new created books |
POST | /books | store | books.store | Create a new book |
GET | /books/{book}/edit | edit | books.edit | Edit specific book |
PUT/PATCH | /books/{book} | update | books.update | Update a specific book |
DELETE | /books{delete} | destroy | books.destroy | Delete a specific book |
I won’t mention all status codes that we’ve got but if you are interested, I recommend you this article from Laravel Daily where they show all available status codes.
Whether you’re making applications in Java, C#, python or you’re making web-based applications in Laravel, there’s a lot you need to think about regarding designing your software. A lot of developers tend to make small mistakes in their JSON file since it’s pretty easy to make small typos. Let’s go over the structure of a JSON file.
{
"id" : 1,
"type": "books",
"attributes": {
"name": "Deathly Hallows",
"publication_year": 2007
},
”relationships": {
}
}
Every JSON document starts with an opening curly brace and ends with a closing curly brace. Inside the fcurly braces, you need to have a comma to show separation
Members
On the left side of the colon (:), we have something which is called a member name (id
, type
, name
). There are some rules that you need to follow when defining members:
• Needs to have at least one character
• You can only use characters
• Member must start and end with global characters
• Keep your member names lowercase
• Use your own convention for snake cases, kebab case or camel case (But be consistent)
Content
The content of the value of the member. In our case, it will be 1
, books
, Deathly Hallows
.
Attributes
You have the option to pass in attributes inside a JSON specification. You can see attributes in here as the same attributes you use on your Laravel models:
• Attribute members should be an object
• Cannot be a relationship
• Attributes can be empty, or even better removed if you don’t use them
• In our example, name
and publication_year
as members inside our attributes
• There’s no need to add an id in your attribute because they are reserved on the root of the resource object (so outside of our attributes)
Relationships
Sometimes you might need a relationship inside your API because data in an application can be related to one another.
Laravel Passport is an Oauth 2.0 server implementation for API authentication using Laravel. IF you have ever worked with Google account, Spotify or Dropbox, you might have seen authentication. That’s being done through Oauth. Laravel Passport is used to secure your API through different ways of authentication.
Laravel Passport needs to be pulled in through Composer. You need to perform the following command from the root of your project directory:
composer require laravel/passport
Laravel Passport interacts with the database because the clients and access tokens need to be stored somewhere. You might think that nothing needs to be migrated since there are no new migrations, but the migrations of Laravel Passport are included in the composer package itself.
php artisan migrate
In order to run Laravel Passport, we need ot make sure that we run the following command to generate client information for us
php artisan passport:install
This will create a Client Id
and Client Secret
Example:
Client ID: 1
Client secret: 8yvyFECEuIVvZAJPmMGsG9EoZvgLfJKNMkaYKcFb
In order to find out which token and scope belongs to which user, we need to make sure that we connect with Laravel Passport in the correct way.
First of all, we need to tell the /bookstore/app/Models/User.php
model that we want to pull in the hasApiTokens class:
use Laravel\Passport\HasApiTokens;
Also, make sure that you use the hasapiTokens as a trait inside your class:
use HasFactory, Notifiable, hasApiTokens;
The next step is to tell our /bookstore/app/Providers/AuthServiceProvider.php
to revoke the access tokens, clients and personal access tokens. Let’s pull in ``Passport``` in the use statement
use Laravel\Passport\Passport;
In order to revoke the right items, we need to call the Passport routes inside the boot method of the /bookstore/app/Providers/AuthServiceProvider.php
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
The last step is to setup Laravel Passport as a driver for our API Authentication. This can be done inside the /bookstore/config/Auth.php
file, where you need to replace ```’driver’ => ‘token’ to:
'api' => [
'driver' => 'passport',
'provider' => 'users',
'hash' => false,
],
Instead of using the /bookstore/routes/web.php
file, we’re going to use the /bookstore/routes/api.php
file to define a new route for our API.
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
In postman, perform a GET
request to ```http://127.0.0.1:8000/api/user.
Whoops, we’ve got an error message. Before your endpoint works successfully, you got to make sure that you pass in the right data inside the headers
tab of Postman. We got to make sure that our KEY is equal to Accept
and the VALUE is application/json
If you perform your request now, you’ll see that the status code is 401
. If the output was the actual user, we would’ve got a huge problem. At the moment, the middleware is telling us that the authentication works properly, but the request has been performed by an unauthorized person.
We need to make sure that we generate a unique access token that will allows us to perform requests.
There are lots of different ways on how you could fix this, and I want to use the two keys we generated in the previous section.
The first step is to perform a POST
request to http://127.0.0.1:8000/oauth/token.
Click on the body
tab inside Postman to pass in information inside the body of your request, and add the following:
grant_type:password
client_id: 1
client_secret: 8yvyFECEuIVvZAJPmMGsG9EoZvgLfJKNMkaYKcFb
username: [USERNAME FROM USERS TABLE]
password: [PASSWORD FROM USER]
scope:
If we save it and perform our request, you will see that the return value is 200 and we got a access token and refresh token.
If we perform the request one more time, you’ll see that the values are different this time.
Copy the access token, navigate to the Headers
tab and create a new KEY VALUE pair.
The KEY will be Authorization
and the value will be Bearer [CODE]
If we perform a GET
request to http://127.0.0.1:8000/api/v1/user, you’ll see all users available in the database.
In order to create Authors for a bookstore, we need to make sure that we’ve got a Author model, database migration, factory and controller. Let’s perform the following command to create it:
php artisan make:model -a -r Author
Open the /bookstore/database/migrations/create_authors_table
migration and replace the ```up()`` method with the following:
public function up()
{
Schema::create('authors', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
We need to make sure that we set column name
to fillable since we’re going to perform a mass assignment.
protected $fillable ['name'];
Let’s migrate our database to create the authors
table:
php artisan migrate
The next step is to add dummy data inside our database to create fake users! Let’s open the /bookstore/database/factories/AuthorFactory.php
and replace the definition()
method with:
public function definition()
{
return [
'name' => $this->faker->name
];
}
In order to use our factory, we got to make sure that we’ve got our database seeder setup
Php artisan make:seeder AuthorsTableSeeder
Open the /database/seeders/AuthorsTableSeeder.php
file and replace the run()
method with the following:
public function run()
{
$this->call(AuthorsTableSeeder::class);
}
Don’t forget to replace the model inside the /database/seeder/DatabaseSeeder.php
file:
public function run()
{
\App\Models\Author::factory(10)->create();
}
Right now, we can migrate and seed our database:
php artisan migrate –seed
Let’s redefine our route /routes/api.php
to the following:
Route::middleware('auth:api')->prefix('v1')->group(function() {
Route::get('/user', function(Request $request){
return $request->user();
});
Route::apiResource('/authors', AuthorsController::class);
Route::apiResource('/books', BooksController::class);
});
Everything after this part is straight forward and will be done inside the AuthorsResource
, Authorscontroller
and BookSController
files.
Thanks to Laravel for giving me the opportunity to make this tutorial on Laravel API