Thursday, 20 June 2024

Creating a basic authentication API using Laravel 11

I have used this blog post to create the API, but with a couple of tweaks and some additional explanations. So, thanks Hendrik!

Pre-requisites

I'll assume that you have Laravel set up to use laravel commands.

First, create your application

Run:
laravel new sanctum-example

To question 'Would you like to install a starter kit?', select 'No starter kit'.
To question 'Which testing framework do you prefer?',  select 'Pest'.
To question 'Would you like to initialize a Git repository?', select 'yes'.
To question 'Which database will your application use?', select 'MySQL'.
To question 'Would you like to run the default database migrations?', select 'no'.

Run:
cd sanctum-example

Run:
php artisan install:api

At this stage, the API script reports: "INFO  API scaffolding installed. Please add the [Laravel\Sanctum\HasApiTokens] trait to your User model".
So, let's do that first.
Open app/Models/User.php and change the line:
use HasFactory, Notifiable;
to:
use HasApiTokens, HasFactory, Notifiable;
This will also require you to add the namespace:
use Laravel\Sanctum\HasApiTokens;

Now that's done:
Delete the resources directory
Delete the file routes/web.php
Delete the line web: __DIR__.'/../routes/web.php', from bootstrap/app.php

At this stage I like to create the database so that migrations can be added.
Run:
php artisan serve
Now open your favourite MySQL editor and create the database, i.e.
CREATE DATABASE `sanctum-example`;
Now we can close the server using Ctrl-c.
We should now edit our database settings in the .env file to something similar to this:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=sanctum-example
DB_USERNAME=root
DB_PASSWORD=<yourpassword>


We're now in a good place to do our migrations:
php artisan migrate

At this stage Sanctum doesn't have a configuration published. For this we have to run:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

Create an Authentication Controller

Our application is API only, so we don't need to create a separate directory to hold API code. So, run:
php artisan make:controller AuthController
We should now have a file called app/Http/Controllers/AuthController.php
To this controller, let's add a method to handle registration:
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
    'name'      => 'required|string|max:255',
    'email'     => 'required|string|max:255|unique:users',
    'password'  => 'required|string'
]);

if ($validator->fails()) {
    return response()->json($validator->errors());
}

$user = User::create([
    'name'      => $request->name,
    'email'     => $request->email,
    'password'  => Hash::make($request->password)
]);

$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
    'data'          => $user,
    'access_token'  => $token,
    'token_type'    => 'Bearer'
]);
}
A method to handle login:
public function login(Request $request)
{
        $validator = Validator::make($request->all(), [
            'email'     => 'required|string|max:255',
            'password'  => 'required|string'
        ]);
        if ($validator->fails()) {
            return response()->json($validator->errors());
        }

        $credentials    =   $request->only('email', 'password');

        if (!Auth::attempt($credentials)) {
            return response()->json([
                'message' => 'User not found'
            ], 401);
        }

        $user   = User::where('email', $request->email)->firstOrFail();
        $token  = $user->createToken('auth_token')->plainTextToken;

        return response()->json([
            'message'       => 'Login success',
            'access_token'  => $token,
            'token_type'    => 'Bearer'
        ]);
}
Finally, a method to handle logout:
public function logout()
{
        Auth::user()->tokens()->delete();
        return response()->json([
            'message' => 'Logout successfull'
        ]);
}
In order to support these methods, you need to have the following namespaces at the top of your class:
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

Create Routes

Next we need to edit the file  routes/api.php
We'll begin by adding our new namespace to the top of the file:
use App\Http\Controllers\AuthController;

Now we can add the routes to support the work we did in the AuthController.
A route for /user should already exist. Don't remove/override that, we'll need it later. Add:
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);
});

Testing our work

Let's start the server again:
php artisan serve
I use Postman for this, but essentially, below are the values you need to perform in each test.

Register

Method: POST
URL: http://127.0.0.1:8000/api/register
Header fields: Accept: application/json
Body fields: name,email,password
Make up your own name, email address and password

Login

Method: POST
URL: http://127.0.0.1:8000/api/login
Headers fields: Accept: application/json
Body fields: email, password

Get User

Method: GET
URL: http://127.0.0.1:8000/api/login
Headers: Accept : application/json
Authorization: <Bearer token>
Make use of the token returned when you used the login or register API.

Logout

Method: POST
URL: http://127.0.0.1:8000/api/logout
Headers: Accept: application/json
Authorization: <Bearer token>
Make use of the token returned when you used the login or register API.

Tuesday, 18 June 2024

Relating data using the API Resource to a Laravel 11 Model

Pre-requisites

We'll assume 3 things:

  1. That the model was created using the approach in this blog post.
  2. That the route was created using the approach in this blog post.
  3. That a migration file has been set using this blog post.
  4. That an Eloquent API Resource has been set up using this blog post.

We're going to create another table called Second which contains options. These options can be set when we create a YourModel record, but we'll also need to retro-set the existing YourModel records with the new Second model values

Part 1: The New Model

We'll begin with a new model. The -m creates a migration file for the model.

php artisan make:model Second -m

Now we'll open the newly created migration file.

Inside the function up(), add the new fields e.g.

$table->string('name')->unique();

This time we're going to specify the record values to be migrated. At the end of the function up(), add:

DB::table('second')->insert([

  [

  'name' => 'Type 1'

  'created_at' => now(),

                'updated_at' => now(),

  ],

  [

  'name' => 'Type 2'

  'created_at' => now(),

                'updated_at' => now(),

  ],

  [

  'name' => 'Type 3'

  'created_at' => now(),

                'updated_at' => now(),

  ],

 ]); 

Part 2: The Connecting Migration

Now we need to make another migration to add SecondModel values to the original YourModel records:

php artisan make:migration add_secondmodel_id_to_yourmodel

A foreign key will be required to link a record in YourModel, to a record on the SecondModel. In this newly created migration we can populate the 

Schema::table within the function up() with:

$table->foreignId('second_id')->nullable()->after('id')->constrained();

Now we'll append the function drop(), by populating the Schema::table:

$table->dropForeign('second_id');

$table->dropColumn('second_id');

Part 3: Updating the First Model

Now in the YourModel file, we need to add 'second_id' to the $fillable array.

The $fillable array is used to prevent unauthorised data manipulation by limiting which attributes can be set on a model.

Now in the YourModel file, we need to add a function second(), which returns

$this->belongsTo(Second::class);

to make the link.

Now we need to open the YourFactory.php and within the function definition() return array, add:

'second_id' => and(0, 1) === 0 ? NULL : Second::pluck('id')->random(),

This will populate the foreign keys of existing records using YourModel with random values from the SecondModel.

We should also update the StoreYourRequest file, by adding to the return array of function rules()

'second_id' => 'nullable|exists:second,id'

then creating:

public function attributes()

    {

        return [

            'second_id' => 'second'

        ];

    }

Part 4: The Second Model Resource

We'll now need to create a resource, so that we only communicate the fields we need through the API:

php artisan make:resource SecondResource

Then reset the return values of the function toArray()

return [

'id' => $this->id,

'name' => $this->name,

        ];

Part 5: Connecting 2 Models

Now on the YourResource, add append Second to the return values of the function toArray(), thus 

'second' => SecondResource::make($this->whenLoaded('second'))

Finally, it's now time to run the migrations:

php artisan migrate:fresh --seed 

Part 6: Seeing the results

Use a tool like Postman to test the first endpoint.
Commonly, when working locally, this would take the form of a GET request to and endpoint of:
http://localhost:8000/api/yourcontrollername

Deleting data using the API Resource to a Laravel 11 Model

Pre-requisites

We'll assume 3 things:

  1. That the model was created using the approach in this blog post.
  2. That the route was created using the approach in this blog post.
  3. That a migration file has been set using this blog post.
  4. That an Eloquent API Resource has been set up using this blog post.


Now open the YourController.php.

Develop the function destroy() like this:

public function destroy(YourModel $yourmodel)

{

$yourmodel->delete();

return response()->noContent();

}

To test this in Postman, you'll need to send a DELETE request with the URL of http://localhost/api/yourcontroller/1

In the Headers tab add the key Accept and the value of application/json

In the body select none instead of raw.

Try using an empty object to see if the API returns the validation message.

That should work.

Updating data using the API Resource to a Laravel 11 Model

Pre-requisites

We'll assume 3 things:

  1. That the model was created using the approach in this blog post.
  2. That the route was created using the approach in this blog post.
  3. That a migration file has been set using this blog post.
  4. That an Eloquent API Resource has been set up using this blog post.

Similar to the approach to add data we will update the Http/Requests/UpdateYourRequest.php.
We can begin by removing everything from within the class.
The we will instead extend the class by StoreYourRequest
Now open the YourController.php and remove the edit method.
In the function update() thus:
public function update(UpdateYourRequest $request, YourModel $yourmodel)
{
$yourmodel->update($request->validated());
return YourResource::make($yourmodel);
}
To test this in Postman, you'll need to send a PUT request with the URL of http://localhost/api/yourcontroller/1
In the Headers tab add the key Accept and the value of application/json
In the body select raw and choose the data format JSON.
Try using an empty object to see if the API returns the validation message
Now try an object like this:
{
"name": "My name again"
}
That should work.

Adding data using the API Resource to a Laravel 11 Model

Pre-requisites

We'll assume 3 things:

  1. That the model was created using the approach in this blog post.
  2. That the route was created using the approach in this blog post.
  3. That a migration file has been set using this blog post.
  4. That an Eloquent API Resource has been set up using this blog post.

This exercise will include adding validation of the incoming data.

When we created the model, we selected Form Requests, which created Http/Requests/StoreYourModelRequest.php and Http/Requests/UpdateYourModelRequest.php.

A Form Request in Laravel is a class that encapsulates the validation logic for a form submission. It allows you to separate the validation logic from your controller and makes it easier to reuse the same validation rules across multiple controllers.

We will edit StoreYourModelRequest.

If we're not using authorization the function authorize() should return true.

Now we will validation rules to the name field, thus:

public function rules()

{

return [

'name' => 'required|string|max:255'

];

}

Now in the YourController, we don't need the create method, so delete it. We just need to have a function store() which looks like this:

public function store()

{

$yourmodel = YourModel::create($request->validated())

return YourResource::make($yourmodel);

}


Now in the your model add the attribute

protected $fillable = ['name'];

$fillable is used to prevent unauthorised data manipulation by limiting which attributes can be set on a model.

To test this in Postman, you'll need to send a POST request with the URL of http://localhost/api/yourcontrollername

In the Headers tab add the key Accept and the value of application/json

In the body select raw and choose the data format JSON.

Try using an empty object to see if the API returns the validation message

Now try an object like this:

{

"name": "My name"

}

That should work.

Using the Eloquent API Resource to streamline data transferred by a Laravel 11 API Model


Eloquent is an Object-Relational Mapping (ORM) system that comes bundled with the Laravel PHP framework. It provides a simple and intuitive way to interact with your database using PHP. Eloquent allows you to define models, which are essentially classes that represent a single table in your database.

Pre-requisites

We'll assume 3 things:

  1. That the model was created using the approach in this blog post.
  2. That the route was created using the approach in this blog post.
  3. That a migration file has been set using this blog post.

Using the Eloquent API Resource to streamline data transferred by a Laravel 11 API Model

The Eloquent API Resource acts as a transformation layer between your eloquent models and the JSON responses. The object of this exercise is to specify only the fields we want to return to the requester, and contain the data within a data object.

To create an API resource run:

php artisan make:resource YourResource

Now the file is in the Resources directory.

Go to the Http/Resources/YourResource.php file

We will edit the function toArray()

This time we will return an associative array of fields e.g.

public function toArray(Request $request): array

{

return [

'id' => $this->id,            

'name' => $this->name,

// Note no description

        ];

}

Listing data using the API Resource

Our next step is to change the function index() in YourController.php, this time returning

YourResource::collection(YourModel::all());

Seeding migrations using factories for a Laravel 11 API Model

Seeding in Laravel is a process that allows you to insert dummy or sample data into a database. 
A factory is a feature that allows you to create fake data for your models, making it easier to test and seed your database with dummy data. Factories are particularly useful for testing, as they provide a way to create predictable and controlled data for your tests.
You'll see below that a factory is set up which describes the type of fake data to be produced during the migration.
Then, in the database seeder, this factory will be called upon for its description, when creating the seeds.

Pre-requisites

We'll assume 3 things:
That the model was created using the approach in this blog post.
That the route was created using the approach in this blog post.
That a migration file has been set using this blog post.

Factory

Now you can open the factory created for your model under database/factories.
This is a good place to add some seed definitions.
In the function definition(), add the field names and the corresponding values to  each field. Here's an example:
public function definition(): array
{
return [
    'name' => $this->faker->name(),
    'description' => $this->faker->sentence(20),
];
}

Seeds

In order to make use of the seed definition you created above, open seeders/DatabaseSeeder.php
public function run(): void
{
YourModel::factory(10)->create();
}

Now in the terminal:
php artisan migrate --seed

Now check in MySQL that the data has been created.
If you followed this blog post the function index(), had some test output of "Hello world" which we tested in Postman. Now we're going to add a listing of the records from our model within our controller and return that as JSON instead. Look for your controller in Http/Controllers/ then edit thus:
public function index()
{
return YourModel::all();
}