<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jonathan Ruiz</title>
    <description>The latest articles on DEV Community by Jonathan Ruiz (@jruizsilva).</description>
    <link>https://dev.to/jruizsilva</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F922404%2F5c850a60-9115-4a74-8ab0-c0dedb0f6767.png</url>
      <title>DEV Community: Jonathan Ruiz</title>
      <link>https://dev.to/jruizsilva</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jruizsilva"/>
    <language>en</language>
    <item>
      <title>Laravel 11 API Rest Auth with jwt-auth</title>
      <dc:creator>Jonathan Ruiz</dc:creator>
      <pubDate>Fri, 26 Jul 2024 06:42:43 +0000</pubDate>
      <link>https://dev.to/jruizsilva/laravel-11-api-rest-auth-with-jwt-auth-nb4</link>
      <guid>https://dev.to/jruizsilva/laravel-11-api-rest-auth-with-jwt-auth-nb4</guid>
      <description>&lt;p&gt;Installing via composer&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

composer require tymon/jwt-auth


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Publishing the config&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This command will create a file in &lt;code&gt;config/jwt.php&lt;/code&gt;&lt;br&gt;
There you can modify the default config like the expiration time of the token&lt;/p&gt;

&lt;p&gt;Generating the secret key&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

php artisan jwt:secret


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This command will add the key JWT_SECRET in your &lt;code&gt;.env&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Adding api guard&lt;br&gt;
Inside the &lt;code&gt;config/auth.php&lt;/code&gt;&lt;br&gt;
Make the following changes&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

'defaults' =&amp;gt; [
    'guard' =&amp;gt; env('AUTH_GUARD', 'api'),
    'passwords' =&amp;gt; env('AUTH_PASSWORD_BROKER', 'users'),
],


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

'guards' =&amp;gt; [
    'web' =&amp;gt; [
         'driver' =&amp;gt; 'session',
         'provider' =&amp;gt; 'users',
    ],
    'api' =&amp;gt; [
        'driver' =&amp;gt; 'jwt',
        'provider' =&amp;gt; 'users',
    ],
],


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Updating the User model&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Adding the require methods&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

public function getJWTIdentifier()
    {
        return $this-&amp;gt;getKey();
    }
public function getJWTCustomClaims()
    {
        return [];
    }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Creating the UserController&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

php artisan make:controller UserController --api --model=User


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Defining the store method&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

public function store(Request $request)
{
    $data = $request-&amp;gt;validate([
        'name' =&amp;gt; 'required',
        'email' =&amp;gt; 'required|email|unique:users',
        'password' =&amp;gt; 'required|min:6',
    ]);
    $data['password'] = bcrypt($data['password']);
    $user = User::create($data);
    return response()-&amp;gt;json($user, 201);
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Defining the me method&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

public function me()
{
    return response()-&amp;gt;json(auth()-&amp;gt;user());
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Creating the AuthController&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

php artisan make:controller AuthController


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Defining the login method&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

public function login(Request $request)
{
    $credentials = $request-&amp;gt;validate([
        'email' =&amp;gt; 'required|email',
        'password' =&amp;gt; 'required'
    ]);
    $token = auth()-&amp;gt;attempt($credentials);

    if (!$token) {
        return response()-&amp;gt;json([
            'status' =&amp;gt; 401,
            'message' =&amp;gt; 'Credenciales incorrectas'
        ], 401);
    }

    return response()-&amp;gt;json([
        'token' =&amp;gt; $token,
        'user' =&amp;gt; auth()-&amp;gt;user(),
        'expire_in' =&amp;gt; auth()-&amp;gt;factory()-&amp;gt;getTTL() * 60
    ]);
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Defining the me method&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

public function me()
{
    return response()-&amp;gt;json(auth()-&amp;gt;user());
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Creating GuestMiddleware&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

php artisan make:middleware GuestMiddleware


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This command will create the middleware in app/Http/Middlewares&lt;br&gt;
import &lt;code&gt;use Illuminate\Support\Facades\Auth;&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

public function handle(Request $request, Closure $next, ...$guards): Response
{
    if (Auth::guard($guards)-&amp;gt;check()) {
        abort(401, 'You are not allowed to access this resource');
    }
    return $next($request);
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This middleware will prevent the users authenticated can access to some routes like login route&lt;/p&gt;

&lt;p&gt;Creating AuthMiddleware&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

php artisan make:middleware AuthMiddleware


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This command will create the middleware in app/Http/Middlewares&lt;br&gt;
import &lt;code&gt;use Illuminate\Support\Facades\Auth;&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

public function handle(Request $request, Closure $next, ...$guards): Response
{
    if (!Auth::guard($guards)-&amp;gt;check()) {
        abort(401, 'You are not allowed to access this resource');
    }
    return $next($request);
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This middleware will prevent the users unauthenticated can access to some routes like me route&lt;/p&gt;

&lt;p&gt;Defining the routes&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

use App\Http\Controllers\UserController;
use App\Http\Controllers\AuthController;
use App\Http\Middleware\GuestMiddleware;
use App\Http\Middleware\AuthMiddleware;

Route::middleware(GuestMiddleware::class)-&amp;gt;group(function () {
    Route::post('login', [AuthController::class, 'login'])-&amp;gt;name('login');
});
Route::middleware(AuthMiddleware::class)-&amp;gt;group(function () {
    Route::get('me', [AuthController::class, 'me'])-&amp;gt;name('me');
});

Route::apiResource('users', UserController::class);


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Creating the AuthTest&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

php artisan make:test AuthTest


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This command will create a file in &lt;code&gt;tests/Feature&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
class AuthTest extends TestCase
{
    use RefreshDatabase;
    private User $user;
    protected function setUp(): void
    {
        parent::setUp();
        $this-&amp;gt;user = User::create([
            'name' =&amp;gt; 'User',
            'email' =&amp;gt; 'user@gmail.com',
            'password' =&amp;gt; '1234',
        ]);
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Tests methods&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

#[Test]
public function should_create_a_user(): void
{
    $data = [
        'name' =&amp;gt; 'Test',
        'email' =&amp;gt; 'test@gmail.com',
        'password' =&amp;gt; '1234',
    ];

    $response = $this-&amp;gt;postJson(route('users.store'), $data);

    $response-&amp;gt;assertStatus(201);
    $this-&amp;gt;assertDatabaseHas('users', ['email' =&amp;gt; 'test@gmail.com']);
}

#[Test]
public function should_login(): void
{
    $data = [
        'email' =&amp;gt; $this-&amp;gt;user-&amp;gt;email,
        'password' =&amp;gt; '1234',
    ];
    $response = $this-&amp;gt;postJson(route('login'), $data);

    $response-&amp;gt;assertStatus(200);
    $response-&amp;gt;assertJsonStructure(['token']);
}

#[Test]
public function should_not_login(): void
{
    $data = [
        'email' =&amp;gt; $this-&amp;gt;user-&amp;gt;email,
        'password' =&amp;gt; '12345',
    ];
    $response = $this-&amp;gt;postJson(route('login'), $data);
    $response-&amp;gt;assertStatus(401);
}

#[Test]
public function should_return_user_authenticated(): void
{
    $response = $this-&amp;gt;actingAs($this-&amp;gt;user)-&amp;gt;getJson(route('me'));
    $response-&amp;gt;assertStatus(200);
    $response-&amp;gt;assertJsonStructure(['id', 'name', 'email']);
    $response-&amp;gt;assertJson(['id' =&amp;gt; $this-&amp;gt;user-&amp;gt;id]);
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Remove comments in the file &lt;code&gt;phpunit.xml&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;env name="APP_ENV" value="testing"/&amp;gt;
&amp;lt;env name="APP_MAINTENANCE_DRIVER" value="file"/&amp;gt;
&amp;lt;env name="BCRYPT_ROUNDS" value="4"/&amp;gt;
&amp;lt;env name="CACHE_STORE" value="array"/&amp;gt;
&amp;lt;env name="DB_CONNECTION" value="sqlite"/&amp;gt;
&amp;lt;env name="DB_DATABASE" value=":memory:"/&amp;gt;
&amp;lt;env name="MAIL_MAILER" value="array"/&amp;gt;
&amp;lt;env name="PULSE_ENABLED" value="false"/&amp;gt;
&amp;lt;env name="QUEUE_CONNECTION" value="sync"/&amp;gt;
&amp;lt;env name="SESSION_DRIVER" value="array"/&amp;gt;
&amp;lt;env name="TELESCOPE_ENABLED" value="false"/&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Running the tests&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

php artisan test --filter AuthTest


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl0w8zg8i0you92sja7ga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl0w8zg8i0you92sja7ga.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>apirest</category>
      <category>auth</category>
      <category>jwt</category>
    </item>
    <item>
      <title>Laravel 11 Simple API Rest with tests</title>
      <dc:creator>Jonathan Ruiz</dc:creator>
      <pubDate>Wed, 24 Jul 2024 08:42:04 +0000</pubDate>
      <link>https://dev.to/jruizsilva/laravel-11-simple-api-rest-with-tests-3f9f</link>
      <guid>https://dev.to/jruizsilva/laravel-11-simple-api-rest-with-tests-3f9f</guid>
      <description>&lt;p&gt;First let’s create the database using phpMyAdmin&lt;br&gt;
database: simple_api_rest&lt;br&gt;
collation: utf8mb4_unicode_ci&lt;/p&gt;

&lt;p&gt;Create a laravel project using one of this alternatives.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer create-project laravel/laravel simple_api_rest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer global require laravel/installer

laravel new simple_api_rest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t select a starter kit.&lt;br&gt;
We will use PHPUnit for testing.&lt;br&gt;
Select mysql as a database.&lt;/p&gt;

&lt;p&gt;Now run the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan install:api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command create a api.php file in routes folter.&lt;br&gt;
Each route defined in that file have the /api prefix by default.&lt;br&gt;
Remove the route that was generated.&lt;/p&gt;

&lt;p&gt;Creating the model Post with their migration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:model Post -m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will create the migration in database/migration&lt;br&gt;
And the model in app/Models&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table-&amp;gt;id();
            $table-&amp;gt;string('title');
            $table-&amp;gt;text('content');
            $table-&amp;gt;timestamps();
        });
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating the PostFactory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:factory PostFactory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will create the factory in database/factories&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function definition(): array
    {
        return [
            "title" =&amp;gt; $this-&amp;gt;faker-&amp;gt;title(),
            "content" =&amp;gt; $this-&amp;gt;faker-&amp;gt;paragraph(),
        ];
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating the PostSeeder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:seeder PostSeeder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will create the seeder in database/seeders&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use App\Models\Post;

class PostSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        Post::factory(10)-&amp;gt;create();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the file seeders/DatabaseSeeder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function run(): void
    {
        $this-&amp;gt;call([
            PostSeeder::class,
        ]);
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the migration and the seeder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan migrate:fresh  # fresh will remove all the data saved
php artisan db:seed # run the seeders
or
php artisan migrate:fresh --seed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see data created in the posts table in phpMyAdmin&lt;br&gt;
Creating a test to view if data was created&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:test PostTest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will create the test in tests/Feature&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use PHPUnit\Framework\Attributes\Test;

class PostTest extends TestCase
{
    #[Test]
    public function posts_must_be_created(): void
    {
        $this-&amp;gt;assertDatabaseCount('posts', 10);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the tests&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan test # run all the tests
or
php artisan test --filter posts_must_be_created # run all test that matches
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using a database in memory to run the tests&lt;br&gt;
Open phpunit.xml and uncomment two lines&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;php&amp;gt;
    &amp;lt;env name="APP_ENV" value="testing"/&amp;gt;
    &amp;lt;env name="APP_MAINTENANCE_DRIVER" value="file"/&amp;gt;
    &amp;lt;env name="BCRYPT_ROUNDS" value="4"/&amp;gt;
    &amp;lt;env name="CACHE_STORE" value="array"/&amp;gt;
    &amp;lt;env name="DB_CONNECTION" value="sqlite"/&amp;gt;
    &amp;lt;env name="DB_DATABASE" value=":memory:"/&amp;gt;
    &amp;lt;env name="MAIL_MAILER" value="array"/&amp;gt;
    &amp;lt;env name="PULSE_ENABLED" value="false"/&amp;gt;
    &amp;lt;env name="QUEUE_CONNECTION" value="sync"/&amp;gt;
    &amp;lt;env name="SESSION_DRIVER" value="array"/&amp;gt;
    &amp;lt;env name="TELESCOPE_ENABLED" value="false"/&amp;gt;
&amp;lt;/php&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we run the tests again will throw an error because the seeder wasn’t executed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PostTest extends TestCase
{
    use RefreshDatabase; // Create the database and run the migrations in each test

    protected function setUp(): void
    {
        parent::setUp();
        $this-&amp;gt;seed(PostSeeder::class); // Run the seeder PostSeeder
    }
    #[Test]
    public function posts_must_be_created(): void
    {
        $this-&amp;gt;assertDatabaseCount('posts', 10);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating controller&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:controller PostController --api --model=Post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will create the controller in app/Http/Controllers&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::all();
        return response()-&amp;gt;json($posts);
    }
    public function store(Request $request)
    {
        $data = $request-&amp;gt;validate([
            'title' =&amp;gt; 'required|string|max:255',
            'content' =&amp;gt; 'required|string',
        ]);
        $post = Post::create($data);
        return response()-&amp;gt;json($post, 201);
    }
    public function show(Post $post)
    {
        return response()-&amp;gt;json($post);
    }
    public function update(Request $request, Post $post)
    {
        $data = $request-&amp;gt;validate([
            'title' =&amp;gt; 'sometimes|required|string|max:255',
            'content' =&amp;gt; 'sometimes|required|string',
        ]);
        $post-&amp;gt;update($data);
        return response()-&amp;gt;json($post);
    }
    public function destroy(Post $post)
    {
        $post-&amp;gt;delete();
        return response()-&amp;gt;json(null, 204);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Defining routes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;

Route::apiResource('posts', PostController::class);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Command to see the routes defined&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan route:list
or
php artisan r:l
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CRUD tests&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PostTest extends TestCase
{
    use RefreshDatabase; // Create the database and run the migrations in each test

    protected function setUp(): void
    {
        parent::setUp();
        $this-&amp;gt;seed(PostSeeder::class); // Run the seeder PostSeeder
    }
    #[Test]
    public function posts_must_be_created(): void
    {
        $this-&amp;gt;assertDatabaseCount('posts', 10);
    }

    #[Test]
    public function must_show_post_list(): void
    {
        $response = $this-&amp;gt;getJson(route('posts.index'));
        $response-&amp;gt;assertStatus(200);
        $response-&amp;gt;assertJsonCount(10);
        $response-&amp;gt;assertJsonStructure([
            '*' =&amp;gt; [
                'id',
                'title',
                'content',
                'created_at',
                'updated_at',
            ]
        ]);
    }

    #[Test]
    public function must_show_post(): void
    {
        $response = $this-&amp;gt;getJson(route('posts.show', 1));
        $response-&amp;gt;assertStatus(200);
        $response-&amp;gt;assertJsonStructure([
            'id',
            'title',
            'content',
            'created_at',
            'updated_at',
        ]);
    }
    #[Test]
    public function must_create_post(): void
    {
        $response = $this-&amp;gt;postJson(route('posts.store'), [
            'title' =&amp;gt; 'Test Post',
            'content' =&amp;gt; 'This is a test post',
        ]);
        $response-&amp;gt;assertStatus(201);
        $response-&amp;gt;assertJsonStructure([
            'id',
            'title',
            'content',
            'created_at',
            'updated_at',
        ]);
        $this-&amp;gt;assertDatabaseHas('posts', [
            'title' =&amp;gt; 'Test Post',
            'content' =&amp;gt; 'This is a test post',
        ]);
    }

    #[Test]
    public function must_update_post(): void
    {
        $response = $this-&amp;gt;putJson(route('posts.update', 1), [
            'title' =&amp;gt; 'Updated Test Post',
            'content' =&amp;gt; 'This is an updated test post',
        ]);
        $response-&amp;gt;assertStatus(200);
        $response-&amp;gt;assertJsonStructure([
            'id',
            'title',
            'content',
            'created_at',
            'updated_at',
        ]);
        $this-&amp;gt;assertDatabaseHas('posts', [
            'id' =&amp;gt; 1,
            'title' =&amp;gt; 'Updated Test Post',
            'content' =&amp;gt; 'This is an updated test post',
        ]);
    }

    #[Test]
    public function must_delete_post(): void
    {
        $response = $this-&amp;gt;deleteJson(route('posts.destroy', 1));
        $response-&amp;gt;assertStatus(204);
        $this-&amp;gt;assertDatabaseMissing('posts', [
            'id' =&amp;gt; 1,
        ]);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running tests&lt;br&gt;
Update and create tests will throw an error&lt;br&gt;
We’re using mass assignment but still not defined the attributes that can received.&lt;/p&gt;

&lt;p&gt;Open Post in app/Models&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'content',
    ];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runing the tests again&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan test
or
php artisan test --filter PostTest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>laravel</category>
      <category>apirest</category>
      <category>testing</category>
    </item>
    <item>
      <title>Nextjs + TailwindCSS + Storybook</title>
      <dc:creator>Jonathan Ruiz</dc:creator>
      <pubDate>Tue, 07 Feb 2023 22:53:55 +0000</pubDate>
      <link>https://dev.to/jruizsilva/nextjs-tailwindcss-storybook-5cmi</link>
      <guid>https://dev.to/jruizsilva/nextjs-tailwindcss-storybook-5cmi</guid>
      <description>&lt;h2&gt;
  
  
  Create a nextjs project.
&lt;/h2&gt;

&lt;p&gt;Using &lt;strong&gt;Javascript&lt;/strong&gt; you can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app@latest
# or
yarn create next-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Using &lt;strong&gt;Typescript&lt;/strong&gt; you can use:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app@latest --typescript
# or
yarn create next-app --typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I'll use this options&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F256h7g4gbjpqu80n4x03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F256h7g4gbjpqu80n4x03.png" alt="create-next-app options selected" width="800" height="116"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Install Tailwind CSS with Next.js
&lt;/h2&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://tailwindcss.com/docs/guides/nextjs" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--TESeWlR1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://tailwindcss.com/api/og%3Fpath%3D/docs/guides/nextjs" height="420" class="m-0" width="800"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://tailwindcss.com/docs/guides/nextjs" rel="noopener noreferrer" class="c-link"&gt;
          Install Tailwind CSS with Next.js - Tailwind CSS
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Setting up Tailwind CSS in a Next.js v10+ project.
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--KJjp50V9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://tailwindcss.com/favicons/favicon-32x32.png%3Fv%3D3" width="32" height="32"&gt;
        tailwindcss.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Trying TailwindCSS and adding a script
&lt;/h2&gt;

&lt;p&gt;I'll try Tailwind CSS modifying the file &lt;code&gt;pages/index.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function Home() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1 className='text-red-900 text-9xl'&amp;gt;hola&amp;lt;/h1&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's add the following script in the &lt;code&gt;package.json&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
    "watch:css": "npx tailwindcss -i ./styles/globals.css -o ./public/tailwind.css --watch"
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script will create file with only the clases that we used in our project. Also it will watch pending for new changes in the classes of our project.&lt;/p&gt;

&lt;p&gt;Let's run the script&lt;br&gt;
&lt;code&gt;yarn watch:css&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;File added by the script&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d6q52fdbo5mqz8gf359.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d6q52fdbo5mqz8gf359.png" alt="File added by the script" width="315" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Classes used in our project are in that file&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7prdjp10sywcx8vnab7l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7prdjp10sywcx8vnab7l.png" alt="classes used in our project" width="686" height="249"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Installing and trying storybook
&lt;/h2&gt;

&lt;p&gt;Before to install storybook we need to rename the &lt;code&gt;.eslintrc.json&lt;/code&gt; to &lt;code&gt;.eslintrc&lt;/code&gt; to avoid conflict with migrating when storybook is installing.&lt;/p&gt;

&lt;p&gt;Now, let's install storybook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx sb init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Storybook installed&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy6uo0rfqt95r89z0gvk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy6uo0rfqt95r89z0gvk.png" alt="Storybook installed" width="528" height="204"&gt;&lt;/a&gt;&lt;br&gt;
Now try storybook running the script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn storybook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If show an error in the file &lt;code&gt;tsconfig.json&lt;/code&gt; close and open vscode&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring storybook with tailwind
&lt;/h2&gt;

&lt;p&gt;Open the file &lt;code&gt;.storybook/preview.js&lt;/code&gt; and import the file created by the tailwindcss script&lt;/p&gt;

&lt;p&gt;It should looks like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import('../public/tailwind.css')

export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now must to tell tailwind to compile the files in the folder stories&lt;br&gt;
Open the file &lt;code&gt;tailwind.config.js&lt;/code&gt; and add the path &lt;code&gt;'./stories/*.{js,ts,jsx,tsx}'&lt;/code&gt;&lt;br&gt;
It should looks like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx}',
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    './stories/*.{js,ts,jsx,tsx}'
  ],
  theme: {
    extend: {}
  },
  plugins: []
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding a storybook script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
  "watch:storybook": "start-storybook dev -p 6006"
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Now let's run both script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn watch:css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, open another terminal and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn watch:storybook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a component and a story
&lt;/h2&gt;

&lt;p&gt;Let's create a component in the path &lt;code&gt;./stories/AnotherButton.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface Props {}

export function AnotherButton(props: Props) {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;button className='p-8 text-lg text-white rounded-3xl bg-slate-700'&amp;gt;
        AnotherButton
      &amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create a story for that component in &lt;code&gt;./stories/AnotherButton.stories.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'

import { AnotherButton } from './AnotherButton'

export default {
  title: 'tailwind/AnotherButton',
  component: AnotherButton
} as ComponentMeta&amp;lt;typeof AnotherButton&amp;gt;

const Template: ComponentStory&amp;lt;typeof AnotherButton&amp;gt; = (args) =&amp;gt; (
  &amp;lt;AnotherButton {...args} /&amp;gt;
)

export const Primary = Template.bind({})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>setup</category>
      <category>nextjs</category>
      <category>tailwindcss</category>
      <category>storybook</category>
    </item>
  </channel>
</rss>
