<?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: Nelson Orina</title>
    <description>The latest articles on DEV Community by Nelson Orina (@nelson_orina_a538ba52e9ed).</description>
    <link>https://dev.to/nelson_orina_a538ba52e9ed</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%2F1658094%2F684a49a1-c4af-4e58-9b13-f29f159f0182.png</url>
      <title>DEV Community: Nelson Orina</title>
      <link>https://dev.to/nelson_orina_a538ba52e9ed</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nelson_orina_a538ba52e9ed"/>
    <language>en</language>
    <item>
      <title>Building a University Resource Sharing Platform: My Laravel Learning Journey</title>
      <dc:creator>Nelson Orina</dc:creator>
      <pubDate>Mon, 05 Jan 2026 09:25:43 +0000</pubDate>
      <link>https://dev.to/nelson_orina_a538ba52e9ed/building-a-university-resource-sharing-platform-my-laravel-learning-journey-579l</link>
      <guid>https://dev.to/nelson_orina_a538ba52e9ed/building-a-university-resource-sharing-platform-my-laravel-learning-journey-579l</guid>
      <description>&lt;p&gt;As part of my software development journey, I'm building the &lt;strong&gt;Strathmore Resource Exchange (SRE)&lt;/strong&gt; - an internal marketplace for university students to share academic resources. Here's what I've learned so far about Laravel development, routing, and file handling. &lt;/p&gt;

&lt;h1&gt;
  
  
  The Project Vision
&lt;/h1&gt;

&lt;p&gt;SRE solves two problem for Strathmore University students:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Outdated academic materials&lt;/strong&gt; - Official exam banks lag years behind&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluttered marketplace&lt;/strong&gt; - Selling items via social media is insecure &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The platform allows students to:&lt;br&gt;
. Share past papers, textbooks, calculators&lt;br&gt;
. Buy/sell items within a trusted ecosystem&lt;br&gt;
. Access verified, recent study materials &lt;/p&gt;
&lt;h1&gt;
  
  
  Tech Stack
&lt;/h1&gt;

&lt;p&gt;. &lt;strong&gt;Backend:&lt;/strong&gt; Laravel 11 (PHP)&lt;br&gt;
. &lt;strong&gt;Frontend:&lt;/strong&gt; Vue 3 with Inertia.js &lt;br&gt;
. &lt;strong&gt;Database:&lt;/strong&gt; MySQL&lt;br&gt;
. &lt;strong&gt;File Handling:&lt;/strong&gt; Spatie Media Library&lt;/p&gt;
&lt;h1&gt;
  
  
  Key Learning #1: Resourceful Routing in Laravel
&lt;/h1&gt;

&lt;p&gt;One of the most powerful Laravel features I discovered is &lt;strong&gt;resourceful routing.&lt;/strong&gt; With just one line of code, you can create 7 complete CRUD routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route::resource('listings', ListingController::class)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single line generated: &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;HTTP Method&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;URL&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Controller Method&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/listings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;index()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Display all listings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/listings/create&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;create()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show form to create a listing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/listings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;store()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Store a new listing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/listings/{listing}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;show()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show a single listing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/listings/{listing}/edit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;edit()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show form to edit a listing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT / PATCH&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/listings/{listing}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;update()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update an existing listing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/listings/{listing}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;destroy()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete a listing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Magic of Route Model Binding
&lt;/h2&gt;

&lt;p&gt;When you type-hint a model in your controller&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 show(Listing $listing)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Laravel automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Takes the ID from the URL (&lt;code&gt;/listings/5&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Finds the Listing with ID 5 &lt;/li&gt;
&lt;li&gt;Injects it into your method &lt;/li&gt;
&lt;li&gt;Returns 404 if not found&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Middleware Group for Security
&lt;/h2&gt;

&lt;p&gt;I learnt to protect routes using middleware groups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route::middleware(['auth','verified'])-&amp;gt;group(function(){
   Route::resource('listings', ListingController::class);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures only authenticated users can access these routes in our marketplace. &lt;/p&gt;

&lt;h1&gt;
  
  
  Key Learning #2: Spatie Media Library for File Handling
&lt;/h1&gt;

&lt;p&gt;Handling file uploads (PDFs for past papers, images for items) was a challenge until I discovered &lt;strong&gt;Spatie Media Library.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Installation &amp;amp; Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require spatie/laravel-medialibrary
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider"
php artisan migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Model Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;

class Listing extends Model implements HasMedia
{
    use InteractsWithMedia;

    public function registerMediaCollections(): void
    {
        $this-&amp;gt;addMediaCollection('past_papers')
            -&amp;gt;acceptsMimeTypes(['application/pdf'])
            -&amp;gt;singleFile();

        $this-&amp;gt;addMediaCollection('listing_images')
            -&amp;gt;acceptsMimeTypes(['image/jpeg', 'image/png'])
            -&amp;gt;singleFile();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Database Structure
&lt;/h3&gt;

&lt;p&gt;Spatie creates a &lt;code&gt;media&lt;/code&gt; table that stores: &lt;br&gt;
. File metadata (name, size, mimetype)&lt;br&gt;
. Polymorphic relationship to any model &lt;br&gt;
. Collection names for organization &lt;/p&gt;
&lt;h3&gt;
  
  
  4. File Upload in Controller
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function store(Request $request)
{
    // Create the listing
    $listing = Listing::create([...]);

    // Handle file based on type
    if ($request-&amp;gt;hasFile('file')) {
        $collection = $request-&amp;gt;type === 'paper' 
            ? 'past_papers' 
            : 'listing_images';

        $listing-&amp;gt;addMediaFromRequest('file')
                -&amp;gt;toMediaCollection($collection);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  5. Retrieving Files
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function show(Listing $listing)
{
    // Get the file URL
    $listing-&amp;gt;file_url = $listing-&amp;gt;getFirstMediaUrl(
        $listing-&amp;gt;type === 'paper' ? 'past_papers' : 'listing_images'
    );

    return view('listing.show', compact('listing'));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Benefits of Spatie Media Library:
&lt;/h2&gt;

&lt;p&gt;. &lt;strong&gt;Automatic file organization -&lt;/strong&gt; Files stored in structured folders &lt;br&gt;
. &lt;strong&gt;Multiple collections -&lt;/strong&gt; Separate past papers from item images &lt;br&gt;
. &lt;strong&gt;Easy retrieval -&lt;/strong&gt; Simple methods to get file URLs &lt;br&gt;
. &lt;strong&gt;Type validation -&lt;/strong&gt; Restrict to PDFs or images &lt;br&gt;
. &lt;strong&gt;Polymorphic relationships -&lt;/strong&gt; Reusable across different models &lt;/p&gt;
&lt;h1&gt;
  
  
  Key Learning #3: Eloquent Relationships &amp;amp; Eager Loading
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Database Structure:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Listings belong to Users and Units
Schema::create('listings', function (Blueprint $table) {
    $table-&amp;gt;id();
    $table-&amp;gt;foreignId('user_id')-&amp;gt;constrained()-&amp;gt;cascadeOnDelete();
    $table-&amp;gt;foreignId('unit_id')-&amp;gt;nullable()-&amp;gt;constrained();
    // ... other fields
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Model Relationships:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Listing extends Model
{
    public function user()
    {
        return $this-&amp;gt;belongsTo(User::class);
    }

    public function unit()
    {
        return $this-&amp;gt;belongsTo(Unit::class);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  The N+1 Query Problem &amp;amp; Solution:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$listings = Listing::all();
foreach ($listings as $listing) {
    echo $listing-&amp;gt;user-&amp;gt;name;  // Makes separate query for EACH listing!
}
// Result: 1 query for listings + N queries for users = N+1 queries

**With eager loading (GOOD):**
$listings = Listing::with(['user', 'unit'])-&amp;gt;get();
// Result: Only 3 queries total!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Key Learning #4: Conditional Validation
&lt;/h1&gt;

&lt;p&gt;For my use case, I needed different validation rules based on listing type: &lt;br&gt;
. &lt;strong&gt;Past papers:&lt;/strong&gt;  Require PDF files (max 10MB) &lt;br&gt;
. &lt;strong&gt;Other items:&lt;/strong&gt; Require images (max 5MB)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Illuminate\Validation\Rule;

$request-&amp;gt;validate([
    'file' =&amp;gt; Rule::when($request-&amp;gt;type === 'paper', [
        'required', 'file', 'mimes:pdf', 'max:10240'
    ], [
        'required', 'image', 'mimes:jpg,jpeg,png', 'max:5120'
    ]),
]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses Laravel's &lt;code&gt;Rule::when()&lt;/code&gt; to apply different validation rules conditionally. &lt;/p&gt;

&lt;h1&gt;
  
  
  Key Learning #5: Inertia.js with Vue
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Server-Side (Laravel Controller):
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function index()
{
    return Inertia::render('Listings/Index', [
        'listings' =&amp;gt; Listing::with(['unit', 'user'])-&amp;gt;paginate(12)
    ]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Client-Side (Vue Component):
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div v-for="listing in listings.data" :key="listing.id"&amp;gt;
    &amp;lt;h2&amp;gt;{{ listing.title }}&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;{{ listing.description }}&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
import { defineProps } from 'vue'
const props = defineProps(['listings'])
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benefits of Inertia.js:
&lt;/h2&gt;

&lt;p&gt;. &lt;strong&gt;Single-page app feel&lt;/strong&gt; without building an API&lt;br&gt;
. &lt;strong&gt;Server-side rendering&lt;/strong&gt; benefits&lt;br&gt;
. &lt;strong&gt;Shared validation errors&lt;/strong&gt; between Laravel and Vue&lt;br&gt;
. &lt;strong&gt;File uploads work seamlessly&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>laravel</category>
      <category>php</category>
      <category>programming</category>
    </item>
    <item>
      <title>Laravel Models, Migrations, &amp; Relationships: Setting Up The 'Post' Feature</title>
      <dc:creator>Nelson Orina</dc:creator>
      <pubDate>Sat, 08 Nov 2025 18:25:04 +0000</pubDate>
      <link>https://dev.to/nelson_orina_a538ba52e9ed/laravel-models-migrations-relationships-setting-up-the-post-feature-4i7</link>
      <guid>https://dev.to/nelson_orina_a538ba52e9ed/laravel-models-migrations-relationships-setting-up-the-post-feature-4i7</guid>
      <description>&lt;p&gt;Welcome back! With secure user authentication in place, we can now focus on building the core functionality of our blog: creating and managing posts. This post will walk you through setting up a new Eloquent Model and its corresponding database table, and establishing the crucial one-to-many relationship between a user and their posts. &lt;/p&gt;

&lt;h2&gt;
  
  
  What We'll Cover Today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Generating the &lt;code&gt;Post&lt;/code&gt; &lt;strong&gt;Model&lt;/strong&gt; and &lt;strong&gt;Migration&lt;/strong&gt; file. &lt;/li&gt;
&lt;li&gt;Defining the necessary database columns for our posts. &lt;/li&gt;
&lt;li&gt;Understanding and setting up the &lt;strong&gt;one-to-many relationship&lt;/strong&gt; between &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Post&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Configure the &lt;code&gt;Post&lt;/code&gt; model's &lt;code&gt;$fillable&lt;/code&gt; property for mass assignment. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1:Generating the Post Model and Migration
&lt;/h2&gt;

&lt;p&gt;Just as the &lt;code&gt;User&lt;/code&gt; model connects our application to the &lt;code&gt;users&lt;/code&gt; table, we need a &lt;code&gt;Post&lt;/code&gt; &lt;strong&gt;Model&lt;/strong&gt; to interact with a future &lt;code&gt;posts&lt;/code&gt; table. Laravel provides a powerful Artisan command to generate both files simultaneously. &lt;/p&gt;

&lt;h3&gt;
  
  
  1.The Artisan Command
&lt;/h3&gt;

&lt;p&gt;Open your terminal in your project's root directory 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;php artisan make:model Post -m 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.Verification
&lt;/h3&gt;

&lt;p&gt;You should now have two new files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;code&gt;app/Models/Post.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration:&lt;/strong&gt;
&lt;code&gt;database/migrations/YYYY_MM_DD_HHMMSS_create_posts_table.php&lt;/code&gt;
(The timestamp ensures migrations run in the correct order).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2:Defining the &lt;code&gt;posts&lt;/code&gt; Database Structure
&lt;/h2&gt;

&lt;p&gt;The migration file is where we design the structure of our &lt;code&gt;posts&lt;/code&gt; table. A blog post needs several key columns, including the content and, critically, a way to link it back to the user who created it. &lt;/p&gt;

&lt;h3&gt;
  
  
  1.Update the Migration File
&lt;/h3&gt;

&lt;p&gt;Open the new migration file(&lt;code&gt;...create_posts_table.php&lt;/code&gt;) and modify the &lt;code&gt;up()&lt;/code&gt; method to look 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;&amp;lt;?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table-&amp;gt;id();
            $table-&amp;gt;timestamps();
            $table-&amp;gt;string('title');
            $table-&amp;gt;longText('body');
            $table-&amp;gt;foreignId('user_id')-&amp;gt;constrained()-&amp;gt;onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.Key Columns Explained
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$table-&amp;gt;id()&lt;/code&gt;:The primary key, a unique auto-incrementing identifier for the post. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$table-&amp;gt;foreignId('user_id')&lt;/code&gt;:This is the &lt;strong&gt;Foreign Key.&lt;/strong&gt; It's the critical link to the &lt;code&gt;users&lt;/code&gt; table. It will store the &lt;code&gt;id&lt;/code&gt; of the user who owns this post. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-&amp;gt;constrained()&lt;/code&gt;:A shortcut that tells Laravel this &lt;code&gt;user_id&lt;/code&gt; column references the &lt;code&gt;id&lt;/code&gt; column on the &lt;strong&gt;plural&lt;/strong&gt; form of the table(&lt;code&gt;users&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onDelete('cascade')&lt;/code&gt;:This is a powerful database-level rule. If a user is deleted, all of their associated posts will also be automatically deleted.This ensures data integrity. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$table-&amp;gt;string('title')&lt;/code&gt;: A simple string column for the post's title. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$table-&amp;gt;text('body')&lt;/code&gt;: A larger text column suitable for the main content of a blog post. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3:Running the Migration
&lt;/h2&gt;

&lt;p&gt;Now that the structure is defined, we run the same command from our previous post to create the actual table in the database. &lt;/p&gt;

&lt;h3&gt;
  
  
  1.The Command
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.Verification
&lt;/h3&gt;

&lt;p&gt;Check your database with a management tool (like HeidiSQL). You should now see a new table named &lt;code&gt;posts&lt;/code&gt; with the columns we defined, including the &lt;code&gt;user_id&lt;/code&gt; foreign key. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Configuring the &lt;code&gt;Post&lt;/code&gt; Model
&lt;/h2&gt;

&lt;p&gt;With the table created, we configure the &lt;code&gt;Post.php&lt;/code&gt; model to safely interact with the database, just as we did for the &lt;code&gt;User&lt;/code&gt; model. &lt;/p&gt;

&lt;h3&gt;
  
  
  1.Set the &lt;code&gt;$fillable&lt;/code&gt; Property
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;app/Models/Post.php&lt;/code&gt; and add the &lt;code&gt;$fillable&lt;/code&gt; property. This protects against unexpected mass assignment by explicitly defining which fields can be filled in a single &lt;code&gt;Post::create([...])&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/Models/Post.php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    // Define the columns that can be mass-assigned
    protected $fillable = [
        'user_id',
        'title',
        'body',
    ];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5:Establishing the Eloquent Relationship
&lt;/h2&gt;

&lt;p&gt;This is the most crucial step for a working blog application. We need to tell Laravel that a single user can have multiple posts, and a post belongs to only one user. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. The &lt;code&gt;user&lt;/code&gt; Model: One-to-Many(Has Many)
&lt;/h3&gt;

&lt;p&gt;A user can have &lt;strong&gt;many&lt;/strong&gt; posts. We define a relationship method named &lt;code&gt;posts&lt;/code&gt; in the &lt;code&gt;User&lt;/code&gt; model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/Models/User.php

// ... other properties and methods

class User extends Authenticatable
{
    // ... other properties

    /**
     * Get the posts for the user.
     */
    public function posts()
    {
        return $this-&amp;gt;hasMany(Post::class);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Benefit:&lt;/strong&gt; Now, you can retrieve all posts by a user like this: &lt;code&gt;$user-&amp;gt;posts&lt;/code&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The &lt;code&gt;Post&lt;/code&gt; Model:Inverse of One-to-Many(Belongs To)
&lt;/h3&gt;

&lt;p&gt;A single post belongs to one user. We define a relationship method named &lt;code&gt;user&lt;/code&gt; in the &lt;code&gt;Post&lt;/code&gt; model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/Models/Post.php

// ... other properties and methods

class Post extends Model
{
    // ... $fillable property

    /**
     * Get the user that owns the post.
     */

 public function user()
    {
        return $this-&amp;gt;belongsTo(User::class);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Benefit:&lt;/strong&gt; Now, you can retrieve the author of any post like this: &lt;code&gt;$post-&amp;gt;user&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up: What We've Accomplished
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;We created the &lt;code&gt;Post&lt;/code&gt; &lt;strong&gt;Model&lt;/strong&gt; and a new &lt;code&gt;posts&lt;/code&gt; table with the essential &lt;code&gt;user_id&lt;/code&gt; foreign key. &lt;/li&gt;
&lt;li&gt;We secured the model using the &lt;code&gt;$fillable&lt;/code&gt; property. &lt;/li&gt;
&lt;li&gt;We established the powerful, two-way, &lt;strong&gt;one-to-many relationship&lt;/strong&gt; between &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Post&lt;/code&gt; using Eloquent methods (&lt;code&gt;hasMany&lt;/code&gt; and &lt;code&gt;belongsTo&lt;/code&gt;). &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the next post, we will use this foundation to build the &lt;strong&gt;Post Creation Controller&lt;/strong&gt; and the &lt;strong&gt;Blade Forms&lt;/strong&gt; to finally let our users add content to the blog. &lt;/p&gt;

</description>
      <category>laravel</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Laravel Models &amp; Authentication: Setting Up User Registration &amp; Login</title>
      <dc:creator>Nelson Orina</dc:creator>
      <pubDate>Sun, 02 Nov 2025 17:28:37 +0000</pubDate>
      <link>https://dev.to/nelson_orina_a538ba52e9ed/laravel-models-authentication-setting-up-user-registration-login-2c79</link>
      <guid>https://dev.to/nelson_orina_a538ba52e9ed/laravel-models-authentication-setting-up-user-registration-login-2c79</guid>
      <description>&lt;p&gt;In our previous tutorial, we built a registration form and handled form submission with a controller. Now, let's take the next step and implement complete user authentication using Laravel's built-in features. &lt;/p&gt;

&lt;h2&gt;
  
  
  What We'll Cover Today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Running Laravel's default migration for authentication&lt;/li&gt;
&lt;li&gt;Understanding the User model and database structure &lt;/li&gt;
&lt;li&gt;Completing our registration functionality &lt;/li&gt;
&lt;li&gt;Adding login/logout features &lt;/li&gt;
&lt;li&gt;Understanding Laravel's security features &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1:Running the Default Authentication Migration
&lt;/h2&gt;

&lt;p&gt;To implement user authentication, the first thing we need is a secure place to store user information, like names, emails, and, most importantly hashed passwords. &lt;/p&gt;

&lt;p&gt;Fortunately, Laravel includes the entire necessary database structure by default. We just need to run the standard migration command to create the tables. &lt;/p&gt;

&lt;h3&gt;
  
  
  1.The Migration Command
&lt;/h3&gt;

&lt;p&gt;Open your terminal in the root directory of your Laravel project and run the following 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 migrate 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.What Just Happened?
&lt;/h3&gt;

&lt;p&gt;When you run &lt;code&gt;php artisan migrate&lt;/code&gt; on a new project, Laravel looks in the &lt;code&gt;database/migrations&lt;/code&gt; directory and executes any pending migration files. The key file for authentication is the one that creates the &lt;code&gt;users&lt;/code&gt; table. &lt;/p&gt;

&lt;p&gt;This table is foundational for Laravel's authentication system and includes all columns we need. &lt;/p&gt;

&lt;h3&gt;
  
  
  3.Verification
&lt;/h3&gt;

&lt;p&gt;To confirm the table was created successfully, you can check by using a database management tool (like Heidi). You should see a new &lt;code&gt;users&lt;/code&gt; table ready to accept registration data. &lt;/p&gt;

&lt;p&gt;With the database structure in place, we can now move on to the code that interacts with this table &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2:Understanding the User Model and Database Structure
&lt;/h2&gt;

&lt;p&gt;In Laravel, the bridge between your application code and your database tables is called an &lt;strong&gt;Eloquent Model&lt;/strong&gt;. For authentication, this role is filled by the default &lt;code&gt;User&lt;/code&gt; model. &lt;/p&gt;

&lt;h3&gt;
  
  
  1.Locating the User Model
&lt;/h3&gt;

&lt;p&gt;The default &lt;code&gt;User&lt;/code&gt; model is located in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/Models/User.php 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.The Model's Role
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;User.php&lt;/code&gt;file is an essential class that does three critical things: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Connects to the Table:&lt;/strong&gt;By default, Laravel models follow a naming convention:a singular model name (&lt;code&gt;User&lt;/code&gt;) is mapped to a plural table name (&lt;code&gt;users&lt;/code&gt;). The &lt;code&gt;User&lt;/code&gt; model knows exactly how to read and write data to the &lt;code&gt;users&lt;/code&gt; table we created in Step 1. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enables Eloquent Features:&lt;/strong&gt;This model gives you access to Laravel's powerful query builder, &lt;strong&gt;Eloquent ORM&lt;/strong&gt;. Instead of writing raw SQL, you can use clean PHP methods like &lt;code&gt;User::all()&lt;/code&gt; or &lt;code&gt;User::create([...])&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implements Authentication Traits:&lt;/strong&gt;You'll notice the &lt;code&gt;User&lt;/code&gt; model uses the &lt;code&gt;Illuminate\Foundation\Auth\User&lt;/code&gt; class and the &lt;code&gt;HasApiTokens&lt;/code&gt;, &lt;code&gt;Notifiable&lt;/code&gt;, and &lt;code&gt;HasFactory&lt;/code&gt; traits. These are built-in Laravel features that handle password hashing, session management, and other security requirements. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.Key Model Properties
&lt;/h3&gt;

&lt;p&gt;Two properties within the &lt;code&gt;User.php&lt;/code&gt; file are important to understand for security and bulk actions: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$fillable:&lt;/code&gt;This array defines the columns in the &lt;code&gt;users&lt;/code&gt; table that can be &lt;strong&gt;mass-assigned&lt;/strong&gt;(i.e., filled in a single operation, like a form submission).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protected $fillable = [
    'name',
    'email',
    'password', // We'll use this in the next step!
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$hidden&lt;/code&gt;:This array specifies any attribute that should not be visible when the model is converted to an array or JSON(like when returning user data in an API response).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protected $hidden = [
    'password', // Crucial: Keeps the hashed password private
    'remember_token',
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the next step, we'll use the &lt;code&gt;$fillable&lt;/code&gt; fields on this &lt;code&gt;User&lt;/code&gt; model to complete our registration process and actually save a new user to the database. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3:Completing Our Registration Functionality
&lt;/h2&gt;

&lt;p&gt;The core of secure registration is taking the data submitted from the form, validating it, and then saving it to the &lt;code&gt;users&lt;/code&gt; table while ensuring the password is &lt;strong&gt;securely hashed&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.Update the Registration Controller
&lt;/h3&gt;

&lt;p&gt;You'll need to modify the method in your registration controller (e.g., &lt;code&gt;RegisteController.php&lt;/code&gt;) that handles the &lt;code&gt;POST&lt;/code&gt; request from the form. &lt;br&gt;
We will use the &lt;code&gt;User&lt;/code&gt; model and the &lt;code&gt;Hash&lt;/code&gt; facade to save the data.&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 

namespace App\Http\Controllers; 

use App\Models\User; 
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Hash; // Import the Hash facade

class RegisterController extends Controller{
   public function register(Register $register){
      $incomingValues = $request-&amp;gt;validate([
'name' =&amp;gt; ['required','min:3', 'max:255'],
'email' =&amp;gt; ['required','email',Rule::unique('users','email')]
'password' =&amp;gt; ['required','min:8'],
])
$user = User::create([
'name' =&amp;gt; $incomingValues['name'],
'email' =&amp;gt; $incomingValues['email'],
'password' =&amp;gt; Hash::make($incomingValues['password']),//Hashing the password before storing it 
]);
auth()-&amp;gt;login($user);//Log the user in immediately
return redirect('/');

}
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Takeaways from the Code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Hash::make($request-&amp;gt;password)&lt;/code&gt;:This is the most important part! Never store raw passwords. Laravel's Hash facade uses strong, one-way encryption to hash the password.The actual password is not recoverable, which keeps your users secure even if your database is compromised.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;User::create([...])&lt;/code&gt;:This uses the Eloquent Model's mass assignment feature(which relies on the &lt;code&gt;$fillable&lt;/code&gt; property we discussed in Step 2) to quickly create and save the new user record. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, a user successfully submitting the registration form will create a secure record in your database. Next, we'll leverage this secure record to implement the full login and logout flow. &lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the Registration
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Fill out your registration form &lt;/li&gt;
&lt;li&gt;Submit and check your  database, you should see a new user with hashed password &lt;/li&gt;
&lt;li&gt;You should be automatically logged in and redirected &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 4:Adding Login and Logout Features
&lt;/h2&gt;

&lt;p&gt;Laravel makes handling sessions and authenticating users remarkably simple using the &lt;code&gt;Auth&lt;/code&gt; facade. &lt;/p&gt;

&lt;h3&gt;
  
  
  1.Implementing the Login Logic(Session Creation)
&lt;/h3&gt;

&lt;p&gt;The login process involves two main steps: displaying the form and processing the credentials. &lt;/p&gt;

&lt;h3&gt;
  
  
  A. The Login Controller Method
&lt;/h3&gt;

&lt;p&gt;You'll need a method in your controller (e.g., LoginController.php) to handle the form submission. We use the &lt;code&gt;Auth::attempt()&lt;/code&gt; method, which automatically does the heavy lifting: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It fetches a user from the database based on the email(or whatever field you set as the authentication identifier). &lt;/li&gt;
&lt;li&gt;It takes the submitted, unhashed password and compares it against the hashed password stored in the database. &lt;/li&gt;
&lt;li&gt;If the credentials match, it creates a secure session for the user.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//In your LoginController.php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; //Import the Auth facade

//..inside the class ...
public function authenticate(Request $request){
//1.Validate the input fields
$credentials = $request-&amp;gt;validate([
'email' =&amp;gt; ['required','email'],
'password' =&amp;gt; ['required'],
]);

//2.Attempt to log the user in 
if(Auth::attempt($credentials)){
$request-&amp;gt;session()-&amp;gt;regenerate();

return redirect('/')-&amp;gt;intended('/dashboard');
}

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Auth::attempt($credentials)&lt;/code&gt;:The magic happens here. It tries to find a user matching the email and verifies the password hash. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$request-&amp;gt;session()-&amp;gt;regenerate()&lt;/code&gt;:This is a security best practice in Laravel to prevent session fixation attacks after a successful login. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redirect()-&amp;gt;intended('/dashboard')&lt;/code&gt;:This is another helpful Laravel feature. It redirects the user to the URL they were trying to access before they were sent to the login page&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.Implementing the Logout Logic(Session Destruction)
&lt;/h3&gt;

&lt;p&gt;Logging a user out is much simpler. It involves destroying the user's current session and revoking their authentication status. &lt;/p&gt;

&lt;h3&gt;
  
  
  A. The Logout Controller Method
&lt;/h3&gt;

&lt;p&gt;You can place this in the same &lt;code&gt;LoginController.php&lt;/code&gt; or a separate &lt;code&gt;LogoutController.php&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;//In your LoginController.php 
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

//...inside the class... 
public function logout(Request $request)
{
   //1.Logout the user
   Auth::logout(); 
   //2.Invalidate the existing session
   $request-&amp;gt;session()-&amp;gt;invalidate(); 
    //3.Regenerate the CSRF token
   $request-&amp;gt;session()-&amp;gt;regenerateToken(); 
   //4.Redirect to the homepage or login page
   return redirect('/');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Auth::logout()&lt;/code&gt;:Clears all authentication information from the current session. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$request-&amp;gt;session()-&amp;gt;invalidate()&lt;/code&gt;:Invalidates the current session, ensuring it can't be reused &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$request-&amp;gt;session()-&amp;gt;regenerateToken()&lt;/code&gt;:Regenerates the Cross-Site Request Forgery (CSRF) token for the next user session. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.Setting up Routes
&lt;/h3&gt;

&lt;p&gt;Finally, make sure you have the routes defined to point to these new controller methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\\In your routes/web.php 

&amp;lt;?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;

Route::get('/', function () {
    return view('home');
});

Route::post('/register', [UserController::class, 'register']);
Route::post('/logout', [LoginController::class,'logout']);
Route::post('/login', [LoginController::class,'login']);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;We use a &lt;code&gt;POST&lt;/code&gt; route for logout, not a &lt;code&gt;GET&lt;/code&gt;. This is another security best practice to prevent attackers from tricking users into logging out via a simple link click. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step5: Understanding Laravel's Security Features
&lt;/h2&gt;

&lt;p&gt;We have built user authentication, but we have also implemented several critical, behind the scenes security measures simply by using Laravel's built-in tools. This step reassures you, the readers, about the robustness of this application. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Password Hashing
&lt;/h3&gt;

&lt;p&gt;As we discussed in Step 3, we use &lt;code&gt;Hash::make()&lt;/code&gt; instead of storing raw passwords. This is the single most important security feature. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How it works:&lt;/strong&gt;Laravel uses a modern, slow hashing algorithm which takes the user's password and transforms it into a long, non-reversible string. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Benefit:&lt;/strong&gt;If your database is ever compromised, the attacker only gets impossible-to-decrypt hash strings, not your users' actual passwords. This protects both your application and your users' security on other websites. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Session Fixation Prevention
&lt;/h3&gt;

&lt;p&gt;Laravel automatically handles the creation and management of user sessions, including defending against one common attack: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Session Fixation:&lt;/strong&gt;This is an attack where a hacker tricks a user into logging in with a session ID the hacker already knows. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your Defense:&lt;/strong&gt;When you include &lt;code&gt;request()-&amp;gt;session()-&amp;gt;regenerate();&lt;/code&gt; in your &lt;code&gt;login&lt;/code&gt; method, you instructed Laravel to generate a brand new, unique session ID after a successful login. This instantly destroys the old, potentially compromised session ID, rendering any hacker's pre-set ID useless. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.Cross-Site Request Forgery (CSRF) Protection
&lt;/h3&gt;

&lt;p&gt;If you're using Laravel forms(or using &lt;code&gt;@csrf&lt;/code&gt; blade directive), you already have powerful protection against CSRF. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What is CSRF?&lt;/strong&gt;It's an attack where a hacker tries to force a logged-in user to submit a malicious request(like changing their password or making a purchase) without their knowledge. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your Defense:&lt;/strong&gt;Laravel requires a hidden, unique &lt;strong&gt;CSRF token&lt;/strong&gt; to be included with every non-GET request (like &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, etc.). If the token doesn't match, Laravel rejects the request automatically. This token is tied to the user's secure session, preventing unauthorized external requests. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.Input Sanitization
&lt;/h3&gt;

&lt;p&gt;While you must still validate your input (which we did), Laravel's framework and its ORM, Eloquent, are inherently designed to protect against &lt;strong&gt;SQL Injection.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL Injection:&lt;/strong&gt;This is an attack where a user inputs malicious SQL code into a form field (like entering &lt;code&gt;' OR 1=1; --&lt;/code&gt; into a username field) to manipulate your database queries. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your Defense:&lt;/strong&gt;When you use Eloquent commands like &lt;code&gt;User::create($incomingFields)&lt;/code&gt;, Laravel automatically sanitizes all input, treating it strictly as data and not as executable code. This makes SQL injection attacks virtually impossible through Eloquent. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up: What We've Accomplished
&lt;/h2&gt;

&lt;p&gt;We have successfully integrated complete user authentication into our application. By following these five steps, we now have a secure functional system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ran the default migration to create the essential &lt;code&gt;users&lt;/code&gt; table. &lt;/li&gt;
&lt;li&gt;Utilized the &lt;code&gt;User&lt;/code&gt; &lt;strong&gt;Eloquent Model&lt;/strong&gt; as the primary interface to our database. &lt;/li&gt;
&lt;li&gt;Secured registration using &lt;code&gt;Hash::make()&lt;/code&gt; to encrypt passwords. &lt;/li&gt;
&lt;li&gt;Implemented smooth &lt;strong&gt;Login&lt;/strong&gt; and &lt;strong&gt;Logout&lt;/strong&gt; with the &lt;code&gt;Auth&lt;/code&gt; facade. &lt;/li&gt;
&lt;li&gt;Gained powerful, passive security from Laravel, including CSRF and SQL Injection protection. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We are now ready to build out the features of our application with confidence, knowing the identity and security of our users are handled.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>laravel</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Laravel Form Handling &amp; Controllers: Building a Registration Form</title>
      <dc:creator>Nelson Orina</dc:creator>
      <pubDate>Sun, 26 Oct 2025 06:07:45 +0000</pubDate>
      <link>https://dev.to/nelson_orina_a538ba52e9ed/laravel-form-handling-controllers-building-a-registration-form-17g4</link>
      <guid>https://dev.to/nelson_orina_a538ba52e9ed/laravel-form-handling-controllers-building-a-registration-form-17g4</guid>
      <description>&lt;p&gt;In our previous post, we set up Laravel and created basic routes and views. Now, let's dive into one of the most fundamental aspects of web development; handling form submissions with controllers. &lt;/p&gt;

&lt;p&gt;Instead of building a complete CRUD application all at once, we'll focus on a single, manageable piece - creating a registration form and processing it with a controller. &lt;/p&gt;

&lt;h2&gt;
  
  
  What We'll Build Today
&lt;/h2&gt;

&lt;p&gt;We'll create a simple user registration form that: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Displays a clean registration form&lt;/li&gt;
&lt;li&gt;Handles form submission with a controller &lt;/li&gt;
&lt;li&gt;Validates user input &lt;/li&gt;
&lt;li&gt;Demonstrates Laravel's security features&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1:Creating the Registration Form View
&lt;/h2&gt;

&lt;p&gt;First navigate to the resources folder then the views folder.&lt;br&gt;
Here create a new file &lt;code&gt;home.blade.php&lt;/code&gt;. Inside this file we'll come up with a simple HTML file that has a registration form.&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;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8"&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
    &amp;lt;title&amp;gt;Simple Blog Registration&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div style="border: 1px solid black;"&amp;gt;
        &amp;lt;h2&amp;gt;Register&amp;lt;/h2&amp;gt;

        &amp;lt;form action="/register" method="post"&amp;gt;
            @csrf
            &amp;lt;input type="text" placeholder="name" name="name"/&amp;gt;
            &amp;lt;input type="email" placeholder="email" name="email"/&amp;gt;
            &amp;lt;input type="password" placeholder="password" name="password"/&amp;gt;
            &amp;lt;button type="submit"&amp;gt;Submit&amp;lt;/button&amp;gt;
        &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Essential &lt;code&gt;@csrf&lt;/code&gt; Directive&lt;/strong&gt;: Notice the line &lt;code&gt;@csrf&lt;/code&gt;. This is a Blade directive that Laravel automatically expands into a hidden input field containing a security token. This token prevents &lt;strong&gt;Cross-Site Request Forgery (CSRF)&lt;/strong&gt; attacks. Anytime you create a &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt; form in Laravel, you must include &lt;code&gt;@csrf&lt;/code&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2:Setting Up Routes
&lt;/h2&gt;

&lt;p&gt;Now, let's configure our routes to handle both displaying the form and processing the submission:&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

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;

Route::get('/', function () {
    return view('home');
});

Route::post('/register', [UserController::class, 'register']);

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation:
&lt;/h3&gt;

&lt;p&gt;We define two routes that look similar but serve completely different purposes because of their &lt;strong&gt;HTTP verb&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Route::get('/')&lt;/code&gt;: A standard GET request, used to simply fetch and display the &lt;code&gt;home&lt;/code&gt; view (the form). &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Route::post('/register')&lt;/code&gt;: A POST request, which is specially designed to send data to the server. This route does not display a view, it directs the data to our Controller for processing. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 3:Creating the Controller
&lt;/h2&gt;

&lt;p&gt;Now we move to the brain of the operation, the Controller. &lt;/p&gt;

&lt;p&gt;To create the controller, run the following Artisan command in your terminal to create the Controller file:&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 UserController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates the file &lt;code&gt;app/Http/Controllers/UserController.php&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code snippet:&lt;/strong&gt;&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

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    public function register(Request $request){
        $incomingFields = $request-&amp;gt;validate([
            'name' =&amp;gt; 'required',
            'email' =&amp;gt; 'required|email',
            'password' =&amp;gt; 'required'
        ]);
        return "Hello from controller";
    }
}  

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Power of the Controller
&lt;/h3&gt;

&lt;p&gt;The most important part of this code is the &lt;code&gt;Request $request&lt;/code&gt; parameter in the method signature. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Request Object:&lt;/strong&gt;When the router directs the data flow here, it packages all the form information, headers, and metadata into a single, powerful object: &lt;code&gt;$request&lt;/code&gt;. The controller's primary job is to interact with this object. &lt;/p&gt;

&lt;p&gt;The first thing we do is call &lt;code&gt;$request-&amp;gt;validate([...])&lt;/code&gt;. This line is doing heavy lifting: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It collects the form fields(name,email,password). &lt;/li&gt;
&lt;li&gt;It checks each field against the rules you defined. &lt;/li&gt;
&lt;li&gt;If any rule fails, Laravel stops the script, sends the user back to the form, and stores the errors for us to display. No need for manual redirection or error handling!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;We have successfully built the full circuit: &lt;strong&gt;View&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Route&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Controller.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Currently, we only validate the data. In the next part, we will discuss &lt;strong&gt;Database Migrations&lt;/strong&gt; and &lt;strong&gt;Eloquent Models&lt;/strong&gt; to set up our user table and complete the &lt;strong&gt;"Create"&lt;/strong&gt; step by securely hashing the password and saving the user record to the database!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>laravel</category>
      <category>beginners</category>
      <category>php</category>
    </item>
    <item>
      <title>Getting Started with Laravel.</title>
      <dc:creator>Nelson Orina</dc:creator>
      <pubDate>Sat, 18 Oct 2025 16:34:42 +0000</pubDate>
      <link>https://dev.to/nelson_orina_a538ba52e9ed/getting-started-with-laravel-4l9l</link>
      <guid>https://dev.to/nelson_orina_a538ba52e9ed/getting-started-with-laravel-4l9l</guid>
      <description>&lt;p&gt;So, you've heard about Laravel, the most popular PHP framework for a reason. It's elegant, powerful, and makes web development a joy. But starting can seem daunting. Worry not! This guide will walk you through creating your very first Laravel application, from setting up your environment to running a "Hello, World" page. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What We'll Cover:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting Up Your Environment&lt;/li&gt;
&lt;li&gt;Installing Laravel &lt;/li&gt;
&lt;li&gt;Understanding the Project Structure &lt;/li&gt;
&lt;li&gt;Running Your Development Server&lt;/li&gt;
&lt;li&gt;Creating Your First Route &amp;amp; View &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 1:Setting Up Your Environment&lt;/strong&gt;&lt;br&gt;
Before we can write any Laravel code, we need the right tools. Laravel requires PHP, a package manager called Composer, and some PHP extensions. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;For Ubuntu/Linux Users&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
If you're using Ubuntu or another Debian-based distribution, open your terminal and follow these steps: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;1.Update Your Package List&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;2.Install PHP with Required Extensions&lt;/em&gt;&lt;/strong&gt; &lt;br&gt;
Laravel requires several PHP extensions. Install them all with :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install php php-cli php-fpm php-json php-common php-mysql php-zip php-gd php-mbstring php-curl php-xml php-bcmath php-json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;3.Install Composer&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
sudo chmod +x /usr/local/bin/composer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;4.Install and Configure a Database&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
For MySQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install mysql-server mysql-client
sudo mysql_secure_installation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install postgresql postgresql-contrib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;5.Verify Your Installation&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You should see version numbers for both. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;For Windows Users&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
The easiest way is to use &lt;strong&gt;Laragon&lt;/strong&gt;: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Download and install &lt;a href="https://laragon.org/download" rel="noopener noreferrer"&gt;Laragon&lt;/a&gt;(choose the "Full" version)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It automatically includes PHP, Composer, MySQL, and more&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Launch Laragon and you're ready to go!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;For Mac Users&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Use &lt;strong&gt;Laravel Herd&lt;/strong&gt; for the simplest setup: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download and install &lt;a href="https://herd.laravel.com/windows" rel="noopener noreferrer"&gt;Laravel Herd&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;It automatically manages PHP versions and includes Composer 
3 . It is incredibly simple and just works &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Manual Installation (Any OS)&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PHP:&lt;/strong&gt;Download from &lt;a href="https://www.php.net/downloads" rel="noopener noreferrer"&gt;php.net&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;2.&lt;strong&gt;Composer:&lt;/strong&gt;Follow instructions at &lt;a href="https://getcomposer.org/download/" rel="noopener noreferrer"&gt;getcomposer.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3.&lt;strong&gt;Database:&lt;/strong&gt;Install MySQL, PostgreSQL, or SQLite&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Verify everything is working:&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2:Installing Laravel&lt;/strong&gt;&lt;br&gt;
Now for the fun part! We'll use Composer to create a new Laravel project. &lt;/p&gt;

&lt;p&gt;1.Open your terminal &lt;br&gt;
2.Navigate to the directory where you want to keep your projects (e.g., &lt;code&gt;cd ~/Sites&lt;/code&gt; or &lt;code&gt;cd C:\Apache\htdocs&lt;/code&gt;).&lt;br&gt;
3.Run the following command to create a new project named &lt;code&gt;my-first-blog&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;composer create-project laravel/laravel my-first-blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will download all the necessary Laravel files and dependencies. It might take a few minutes. &lt;br&gt;
Once it's done navigate into your new directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd my-first-blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3:A Quick Look at the Project Structure&lt;/strong&gt;&lt;br&gt;
Let's open the &lt;code&gt;my-first-blog&lt;/code&gt; folder in any code editor(like VSCode). Here are the key folders you will work with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app/&lt;/code&gt;: The heart of your application. This is where you'll write your models, controllers, and core logic. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config/&lt;/code&gt;: Contains all the configuration files for your app(database, mail, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;database/&lt;/code&gt;: Houses your database migrations, seeders, and factories. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;public/&lt;/code&gt;: The web server's document root. Your &lt;code&gt;index.php&lt;/code&gt; file is here. This is the only folder accessible to the public.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resources/&lt;/code&gt;: Contains your views(Blade templates), CSS, and JavaScript (if using Vite).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;routes/&lt;/code&gt;: This is where you define your application's endpoints. The &lt;code&gt;web.php&lt;/code&gt; file is where we'll start. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4:Running Your Development Server&lt;/strong&gt;&lt;br&gt;
Laravel comes with a built-in development server that's perfect for local testing.&lt;/p&gt;

&lt;p&gt;Make sure you are in your project's root directory (&lt;code&gt;my-first-blog&lt;/code&gt;) in the 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;php artisan serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Artisan&lt;/strong&gt; is Laravel's command-line tool. &lt;br&gt;
The terminal will output something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting Laravel development server: http://127.0.0.1:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your web browser and go to &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt;. You should see the default Laravel welcome page. &lt;br&gt;
Congratulations! Your Laravel application is now live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5:Creating Your First Route and View&lt;/strong&gt;&lt;br&gt;
Let's move beyond the default page and create a simple "Hello, World!" page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;1.Define a Route:&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Open the &lt;code&gt;routes/web.php&lt;/code&gt; file. You'll see a default route. Let's add a new one.&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

use Illuminate\Support\Facades\Route;

//This is the default route that shows the welcome page
Route::get('/', function(){
   return view('welcome');
});

//This is our new route
Route::get('/hello', function(){
   return "Hello, World!";
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file. Now, go to &lt;code&gt;http://127.0.0.1:8000/hello&lt;/code&gt; in your browser. You should see the plain text "Hello, World!".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;2.Create a View (A Proper HTML Page):&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Returning plain text is fine, but let's create a proper HTML page. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;code&gt;resources/views/&lt;/code&gt; folder. &lt;/li&gt;
&lt;li&gt;Create a new file called &lt;code&gt;hello.blade.php&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Add some simple HTML to this file:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
   &amp;lt;head&amp;gt;
      &amp;lt;meta charset="UTF-8"&amp;gt;
      &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
      &amp;lt;title&amp;gt;My First Laravel Page&amp;lt;/title&amp;gt; 
   &amp;lt;/head&amp;gt;
   &amp;lt;body&amp;gt;
      &amp;lt;h1&amp;gt;Hello from my first Laravel View&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;This is HTML, served from a Blade template.&amp;lt;/p&amp;gt;
   &amp;lt;/body&amp;gt; 
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;&lt;em&gt;3.Update the Route to Use the View:&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Now, go back to &lt;code&gt;routes/web.php&lt;/code&gt; and change the &lt;code&gt;/hello&lt;/code&gt; route to return this view instead of the plain text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route::get('/hello',function(){
   return view('hello');//This points to resources/views/hello.blade.php
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refresh your browser at &lt;code&gt;http:/127.0.0.1:8000/hello&lt;/code&gt;. You should now see your styled HTML page!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's Next?&lt;/strong&gt;&lt;br&gt;
You've successfully set up your environment, installed Laravel, and created your first custom page! From here, you can expore: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Blade Templating&lt;/strong&gt; for more dynamic views&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Controllers&lt;/strong&gt; to organize your code better&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database &amp;amp; Eloquent ORM&lt;/strong&gt; to work with data&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Introduction to Playwright.</title>
      <dc:creator>Nelson Orina</dc:creator>
      <pubDate>Sat, 04 Oct 2025 13:29:50 +0000</pubDate>
      <link>https://dev.to/nelson_orina_a538ba52e9ed/introduction-to-playwright-10b8</link>
      <guid>https://dev.to/nelson_orina_a538ba52e9ed/introduction-to-playwright-10b8</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The web today isn't what it used to be. For years, I relied on &lt;code&gt;request&lt;/code&gt; and &lt;code&gt;BeautifulSoup&lt;/code&gt; to extract data from the web.It worked perfectly until it didn't. I hit a wall when websites started loading content dynamically with JavaScript. That's when I discovered &lt;code&gt;Playwright&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Playwright?&lt;/strong&gt;&lt;br&gt;
Playwright is an open-source automation framework developed by Microsoft. It allows you to control modern browsers with simple code. Instead of just fetching static HTML Playwright: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Renders the full page, executing all JavaScript just like a human user. &lt;/li&gt;
&lt;li&gt;Interacts with elements like clicking buttons, filling forms, scrolling, and even waiting for content to load as needed. &lt;/li&gt;
&lt;li&gt;Works consistently across all modern browsers.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why Use Playwright?&lt;/strong&gt;&lt;br&gt;
Here are just a few things you can do with it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scrape dynamic websites that load content via JavaScript &lt;/li&gt;
&lt;li&gt;Write end-to-end test for complex web applications. &lt;/li&gt;
&lt;li&gt;Automate repetitive tasks like form submissions or screenshots. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this first post, I'm going to document my first steps with Playwright. We'll move from theory to practice by installing it and writting a simple script to scrape a site that would have been impossible with my old toolkit. &lt;/p&gt;
&lt;h1&gt;
  
  
  Getting Started with Playwright
&lt;/h1&gt;

&lt;p&gt;Now that we know why Playwright matters, let's actually install it and run our first script. I'll use Python here, but Playwright also works with Node.js, Java and .NET. &lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Install Playwright
&lt;/h2&gt;

&lt;p&gt;First, let's install the Playwright package using pip:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Fxit1lkrfclsvibbc6vr2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxit1lkrfclsvibbc6vr2.png" alt=" " width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once installed, we need to install the browser binaries. Playwright can control Chromium, Firefox, and WebKit(Safari):&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Fy9spmd4yi6ms8lmikfit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fy9spmd4yi6ms8lmikfit.png" alt=" " width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This command installs the necessary browsers so Playwright can control them.                 &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge Encountered: Missing Host Dependencies&lt;/strong&gt; &lt;br&gt;
After running the playwright install command, I encountered a warning that initially confused me. While the browsers were successfully downloaded and placed in the cache, the Playwright host system validation noted that my Linux operating system was missing essential libraries needed for the browsers to actually run. &lt;/p&gt;

&lt;p&gt;Here is the exact warning I received:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Playwright Host validation warning: 
╔══════════════════════════════════════════════════════╗
║ Host system is missing dependencies to run browsers. ║
║ Please install them with the following command:      ║
║                                                      ║
║      sudo playwright install-deps                    ║
║                                                      ║
║ Alternatively, use apt:                              ║
║      sudo apt-get install libwoff1\                  ║
║      ... [and a long list of other libraries]        ║
║                                                      ║
╚══════════════════════════════════════════════════════╝
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Resolving the Dependency Issue&lt;/strong&gt;&lt;br&gt;
The good news is that the Playwright team anticipated this and provided two clear solutions. &lt;/p&gt;
&lt;h3&gt;
  
  
  Solution A: Using the Recommended Playwright Command
&lt;/h3&gt;

&lt;p&gt;Playwright provides a single, easy-to-use command specifically for installing these system dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo playwright install-deps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I attempted to run this command outside of my virtual environment, but it immediately failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;orina@Orina:~$ sudo playwright install-deps 
[sudo] password for orina: 
sudo: playwright: command not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue here is that the playwright command is installed within my isolated Python virtual environment &lt;code&gt;(venv)&lt;/code&gt;. Even though I was running it with sudo, my system's global PATH did not include the virtual environment's binary directory, leading to the command not found error.&lt;/p&gt;

&lt;p&gt;To correctly use this command, I would have needed to either: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Use the absolute path to the executable inside the venv: sudo /path/to/venv/bin/playwright install-deps&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Or, run the command while inside the venv and use the -E flag with sudo to preserve the environment variables: sudo -E playwright install-deps&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Solution B: Manually Installing Dependencies with apt (The Fix I Used)
&lt;/h3&gt;

&lt;p&gt;Rather than debugging the path issue, the most reliable and straightforward fix was to use the list of packages provided for my Linux distribution and installing the directly onto the hosts system using &lt;code&gt;apt-get&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;sudo apt-get install libwoff1 libvpx9 libevent-2.1-7t64 libgstreamer-gl1.0-0 libgstreamer-plugins-bad1.0-0 libwebpdemux2 libharfbuzz-icu0 libenchant-2-2 libsecret-1-0 libhyphen0 libmanette-0.2-0 libflite1 gstreamer1.0-libav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Writing Our First Playwright Script
&lt;/h2&gt;

&lt;p&gt;Now that we have all dependencies installed, let's create our first Playwright script. I'll break this down into manageable parts with detailed explanations. &lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;first_scraper.py&lt;/code&gt; and let's build it step by step. &lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1: Import and Basic Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from playwright.sync_api import sync_playwright

def main(): 
   #The sync_playwright() context manager handles browser lifecycle 
   with sync_playwright() as p: 

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;from playwright.sync_api import sync_playwright&lt;/em&gt;: We import the synchronous API. Playwright also has an async API, but we're starting with synchronous for simplicity. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;with sync_playwright() as p&lt;/em&gt;: This context manager automatically handles starting and stopping Playwright. The p variable gives us access to the Playwright instance. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part 2: Launching the Browser
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Launch a Chromium browser instance 
#headless=False means we'll see the browser window 
browser = p.chromium.launch(headless=False)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;p.chromium.launch(headless=False)&lt;/em&gt;: This launches a Chromium browser. 

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;headless=False&lt;/em&gt; means the browser window will be visible. For production scripts, you'd set this to &lt;em&gt;True&lt;/em&gt; to run in the background. &lt;/li&gt;
&lt;li&gt;You could also use &lt;em&gt;p.firefox.launch()&lt;/em&gt; or &lt;em&gt;p.webkit.launch()&lt;/em&gt; for other browsers. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The browser variable represents the browser instance we'll work with. &lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part 3: Creating a Page and Navigation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Create a new page (tab) in the browser
page = browser.new_page()

##Navigate to a website
page.goto('https://webscraper.io/test-sites/e-commerce/more')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;browser.new_page()&lt;/em&gt;: Creates a new tab/page in the browser. Most of our interactions will happen through this page object. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;page.goto('&lt;a href="https://webscraper.io/test-sites/e-commerce/more'" rel="noopener noreferrer"&gt;https://webscraper.io/test-sites/e-commerce/more'&lt;/a&gt;)&lt;/em&gt;: Tells the browser to navigate to the specified URL. Playwright automatically waits for the page to load. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part 4: Interacting with the Page
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Get the page title and print it 
title = page.title()
print(f"Page title: {title}")

#Take a screenshot of the entire page 
page.screenshot(path='screenshot.png')
print("Screenshot saved as 'screenshot.png'")

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;page.title()&lt;/em&gt;: Returns the title of the current page (what you see in the browser tab). &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;page.screenshot(path='screenshot.png')&lt;/em&gt;: Captures a screenshot of the entire visible page and saves it to a file. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part 5: Finding and Extracting Content
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Extract the main heading using a CSS selector 
heading = page.text_content('h1')
print(f"Main heading: {heading}")

#Extract all paragraph text 
paragraphs = page.query_selector_all('p') 
print("Paragraphs found:")
for i, paragraph in enumerate(paragraphs,1): 
   print(f"{i}. {paragraph.text_content()}")

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;page.text_content('h1')&lt;/em&gt;: Finds the first &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; element and extracts its text content. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;page.query_selector_all('p')&lt;/em&gt;: Finds ALL &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; elements on the page and returns a list. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;enumerate(paragraphs,1)&lt;/em&gt;: Loops through the paragraphs with numbering starting at 1. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;paragraph.text_content()&lt;/em&gt;: Extracts text from each paragraph element. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part 6: Cleanup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   #Close the browser
   print("Closing browser...")
   browser.close()

if __name__ == '__main__':
  main()


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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;browser.close()&lt;/em&gt;: Properly closes the browser and releases system resources. This is important to prevent memory leaks. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;if &lt;strong&gt;name&lt;/strong&gt; = '&lt;strong&gt;main&lt;/strong&gt;':&lt;/em&gt; Standard Python practice that ensures the &lt;code&gt;main()&lt;/code&gt; function only runs when the script is executed directly. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Complete Script
&lt;/h3&gt;

&lt;p&gt;Here's the complete script put together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from playwright.sync_api import sync_playwright

def main():
    with sync_playwright() as p:

        browser = p.chromium.launch(headless = False)

        page = browser.new_page()
        page.goto('https://webscraper.io/test-sites/e-commerce/more')

        title = page.title()
        print(f"Page title: {title}")

        page.screenshot(path='screenshot.png')
        print("Screenshot saved as 'screenshot.png'.")

        heading = page.text_content('h1')
        print(f"Main heading: {heading}")

        paragraphs = page.query_selector_all('p')
        print ("Paragraphs found:")
        for i, paragraph in enumerate(paragraphs, 1): 
            print(f"{i}. {paragraph.text_content()}")

        print("Closing browser...")
        browser.close()

if __name__ == "__main__":
    main()

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running Our Script
&lt;/h3&gt;

&lt;p&gt;Save the file and run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python first_scraper.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A browser window opening and navigating to ---&lt;/li&gt;
&lt;li&gt;Output in your terminal showing the page title, heading, and paragraphs. &lt;/li&gt;
&lt;li&gt;A screenshot file created in your directory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the output on the terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0bx7veebl8o5brfynnpo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0bx7veebl8o5brfynnpo.png" alt=" " width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the screenshot taken:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1e6q3r5u6lyu50b486qj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1e6q3r5u6lyu50b486qj.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Seeing Playwright's Real Power
&lt;/h2&gt;

&lt;p&gt;While our first script works great, you might be thinking: "This looks similar to what I could do with requests + BeautifulSoup." And you'd be right; for this simple example. &lt;/p&gt;

&lt;p&gt;But remember the dynamic content problem I mentioned in the introduction? Let me show you why Playwright is truly special. Try this quick experiment: &lt;/p&gt;

&lt;h3&gt;
  
  
  Create a file called &lt;code&gt;requests_vs_playwright.py&lt;/code&gt;:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import requests 
from bs4 import BeautifulSoup 
from playwright.sync_api import sync_playwright

def compare_versions():

    """Compare JavaScript vs non-JavaScript versions of the same site"""

    print("COMPARISON: JavaScript vs Static Content")

    # Test the JavaScript version with requests (will fail)
    print("1.Testing Request on JavaScript version:")

    response = requests.get('https://quotes.toscrape.com/js/')
    soup = BeautifulSoup(response.content, 'html.parser')
    quotes = soup.find_all('div', class_='quote')
    print(f"Quotes found: {len(quotes)}")
    print(" Requests cannot see JavaScript rendered content")

    # Test the non-JavaScript version with requests
    print("2.Testing Request on non-JavaScript version:")
    response = requests.get('https://quotes.toscrape.com/')
    soup = BeautifulSoup(response.content, 'html.parser')
    quotes = soup.find_all('div', class_='quote')
    print(f"Quotes found: {len(quotes)}")
    print(" Requests can see static content")

    # Test the JavaScript version with Playwright
    print("3.Testing Playwright on JavaScript version:")
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto('https://quotes.toscrape.com/js/')
        quotes = page.query_selector_all('div.quote')
        print(f"Quotes found: {len(quotes)}")
        print(" Playwright can see JavaScript rendered content")

        browser.close()

if __name__ == "__main__":
    compare_versions()

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

&lt;/div&gt;



&lt;p&gt;In this file we are scrapping data from two similar sites the difference is one of the sites renders its content using JavaScript while the other utilizes a static html file. From the output of this file when you run &lt;code&gt;python requests_vs_playwright.py&lt;/code&gt; you will notice that the first test case that is scrapping using requests &amp;amp; BeautifulSoup finds 0 quotes, on the other hand the third test case that is scrapping the same JavaScript webpage using Playwright finds all the quotes even though they are being rendered using JavaScript. The second test case is using requests &amp;amp; BeautifulSoup but it scrapping a webpage that utilizes static html files, that is why it is able to find the quotes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The JavaScript version ('/js/') loads quotes dynamically after the page loads&lt;/li&gt;
&lt;li&gt;Requests only sees the initial HTML skeleton - no quotes &lt;/li&gt;
&lt;li&gt;Playwright waits for JavaScript to execute and sees the complete page &lt;/li&gt;
&lt;li&gt;This is exactly the problem that made me switch to Playwright &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please try running this script on your own and observe the difference.  &lt;/p&gt;

&lt;p&gt;Expected output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fgg5gra0j2cjqd7d4r8r4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fgg5gra0j2cjqd7d4r8r4.png" alt=" " width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What We've Accomplished
&lt;/h2&gt;

&lt;p&gt;In this first journey with Playwright, we've: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identified the problem with traditional scraping tools for modern web applications. &lt;/li&gt;
&lt;li&gt;Successfully installed Playwright and resolved real world dependency issues.&lt;/li&gt;
&lt;li&gt;Written our first automation script that can navigate, extract content, and take screenshots. &lt;/li&gt;
&lt;li&gt;Proven Playwright's superiority for dynamic content with an undeniable side-by-side comparison. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important takeaways is playwright sees what requests cannot and it renders JavaScript exactly like a human user, making previously "unscrapable" websites accessible again. &lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next in Our Playwright Journey
&lt;/h2&gt;

&lt;p&gt;This is just the beginning. In the next posts, we'll explore: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Interactice scraping - clicking buttons,  filling forms, handling dropdowns.&lt;/li&gt;
&lt;li&gt;Smart waiting strategies - waiting for elements, network idle, and custom conditions. &lt;/li&gt;
&lt;li&gt;Authentication handling - logging into websites and maintaining sessions. &lt;/li&gt;
&lt;li&gt;Advanced data extraction - tables, pagination, and complex data structures &lt;/li&gt;
&lt;li&gt;Performance optimization - making our scripts faster and more reliable. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stay tuned for the next post where we'll make our script truly interactive. &lt;/p&gt;

&lt;p&gt;In the meantime, I'd love to hear about yout experiences with Playwright. Have you encounteres other dynamic content challenges? What websites are you thinking of automating? Let me know in the comments. &lt;/p&gt;

</description>
      <category>playwright</category>
      <category>webscraping</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Mastering BeautifulSoup: Parsing, Navigating, and Extracting Data Like a Pro</title>
      <dc:creator>Nelson Orina</dc:creator>
      <pubDate>Mon, 29 Sep 2025 18:35:43 +0000</pubDate>
      <link>https://dev.to/nelson_orina_a538ba52e9ed/mastering-beautifulsoup-parsing-navigating-and-extracting-data-like-a-pro-enl</link>
      <guid>https://dev.to/nelson_orina_a538ba52e9ed/mastering-beautifulsoup-parsing-navigating-and-extracting-data-like-a-pro-enl</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In our first tutorial, we learnt the basics of web scraping. Now, let's dive deeper into BeautifulSoup's powerful features.  &lt;/p&gt;


&lt;h1&gt;
  
  
  1. Finding Elements: &lt;code&gt;find()&lt;/code&gt; vs &lt;code&gt;find_all()&lt;/code&gt;
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Single vs Multiple Elements
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="product"&amp;gt;
   &amp;lt;h3 class="title"&amp;gt;Product 1&amp;lt;/h3&amp;gt;
   &amp;lt;a href="/product1"&amp;gt;View Product&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="product"&amp;gt;
   &amp;lt;h3 class="title"&amp;gt;Product 2&amp;lt;/h3&amp;gt;
   &amp;lt;a href="/product2"&amp;gt;View Product&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;

#find() - returns FIRST matching element
first_product = soup.find('div', class_='product')

print(first_product)
#Output is the first div 

#find_all() - returns LIST of all matching elements
all_products = soup.find_all('div', class_='product')

print(all_products)
# Output: [&amp;lt;div class="product"&amp;gt;...&amp;lt;/div&amp;gt;, &amp;lt;div class="product"&amp;gt;...&amp;lt;/div&amp;gt;]

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

&lt;/div&gt;

&lt;h2&gt;
  
  
  Practical Examples
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Get the first h1 tag
main_title = soup.find('h1')

#Get all paragraph tags
all_paragraphs = soup.find_all('p')

#Get all links
all_links = soup.find_all('a')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  2. CSS Selectors with &lt;code&gt;select()&lt;/code&gt; and &lt;code&gt;select_one()&lt;/code&gt;
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Powerful Selection Methods
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="product"&amp;gt;
   &amp;lt;h3 class="title"&amp;gt;Product 1&amp;lt;/h3&amp;gt;
   &amp;lt;a href="/product1"&amp;gt;View Product&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="product"&amp;gt;
   &amp;lt;h3 class="title"&amp;gt;Product 2&amp;lt;/h3&amp;gt;
   &amp;lt;a href="/product2"&amp;gt;View Product&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;

#select_one() - returns first match (like find())
first_product = soup.select_one('.product')

print(first_product)
#Output is the div holding product one 

#select() - returns all matches (like find_all())
all_products = soup.select('.product')
print(all_products)
#Output is both divs in this case as they are of the same class. 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  CSS Selector Examples
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Select by class 
products = soup.select('.product-item')

#Select by ID 
header = soup.select('#main-header') 

#Complex selectors
featured_products = soup.select('div.product.featured')
product_titles = soup.select('div.product &amp;gt; h3.title')

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

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;select()&lt;/code&gt; is often more flexible than &lt;code&gt;find_all()&lt;/code&gt; because you can use full CSS selectors, including nested element targeting.&lt;/p&gt;
&lt;h1&gt;
  
  
  3. Extracting Attributes and Data
&lt;/h1&gt;

&lt;p&gt;Once we've located elements, the next step is usually to extract the actual information inside them such as text, links, or image URLs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Extracting Text From Elements
&lt;/h2&gt;

&lt;p&gt;Often, we just need the text content inside an element. You can access this using the &lt;code&gt;.text&lt;/code&gt; or &lt;code&gt;.get_text()&lt;/code&gt;:&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Attributes
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="product"&amp;gt;
   &amp;lt;h3 class="title"&amp;gt;Product 1&amp;lt;/h3&amp;gt;
   &amp;lt;a href="/product1"&amp;gt;View Product&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="product"&amp;gt;
   &amp;lt;h3 class="title"&amp;gt;Product 2&amp;lt;/h3&amp;gt;
   &amp;lt;a href="/product2"&amp;gt;View Product&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;

product_titles = soup.select('div.product &amp;gt; h3.title')

for title in product_titles:
   print(title.text)

#Output:
#Product 1
#Product 2

Both .text and .get_text() work the same way for most cases, so you can use whichever you prefer.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Extracting Attributes (href,src,etc.)
&lt;/h2&gt;

&lt;p&gt;If you need to get the value of an attribute (like a link's href or an image's src), use .get():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Extract all links and print their href values 
links = soup.find_all('a')
for link in links:
   print(link.get('href'))

#Output:
# /product1
# /product2

images = soup.find_all('img')
for img in images:
   print(img.get('src'))

#Output:
#/images/product1.png
#/images/product2.png

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pro Tip:
&lt;/h2&gt;

&lt;p&gt;You can combine both text and attributes. For example, printing both the product name and its link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;products = soup.select('div.product')
for product in products:
   title = product.select_one('h3.title').text
   link = product.select_one('a').get('href')
   print(f"{title} -&amp;gt; {link}")

#Output: 
# Product 1 → /product1
# Product 2 → /product2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the typical pattern you'll use when building real scrappers, find the container element, then drill down into it to extract the data you need. &lt;/p&gt;

&lt;h1&gt;
  
  
  4.Handling Missing Data Gracefully
&lt;/h1&gt;

&lt;p&gt;Sometimes websites may have missing fields, and trying to access them directly can cause AttributeError. A helper function like this ensures your scraper doesn’t break when data is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safe Data Extraction Techniques
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def safe_extract(element, selector, default="Not available"):
    """Safely extract text from element with fallback"""
    found = element.find(selector)
    return found.text.strip() if found else default

# Usage in scraping loop
for product in products:
    name = safe_extract(product, 'h3')
    price = safe_extract(product, '.price', '$0.00')
    rating = safe_extract(product, '.rating', 'No rating')

    # Extract optional attributes safely
    image = product.find('img')
    image_src = image.get('src') if image else 'default.jpg'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  5.Error Handling Pattern
&lt;/h1&gt;

&lt;p&gt;Always wrap network calls in try/except blocks to prevent your script from crashing on connection issues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try:
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')

    products = soup.find_all('div', class_='product')
    if not products:
        print("No products found - check your selectors!")

except Exception as e:
    print(f"Error during scraping: {e}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;In this tutorial, we moved beyond the basics and learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to use &lt;code&gt;find()&lt;/code&gt;/&lt;code&gt;find_all()&lt;/code&gt; and CSS selectors (&lt;code&gt;select()&lt;/code&gt;/&lt;code&gt;select_one()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;How to extract text and attributes from elements &lt;/li&gt;
&lt;li&gt;How to combine data points for structured results&lt;/li&gt;
&lt;li&gt;How to handle missing elements safely &lt;/li&gt;
&lt;li&gt;How to implement error handling for more reliable scrapers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these techniques, you can now confidently scrape more complex websites and built robust, maintainable web scrapers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Complete End to End Example
&lt;/h1&gt;

&lt;p&gt;Here is a simple scrapper that takes data from a sample e-commerce platform and prints out the details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#importing the BeautifulSoup library and requests module
from bs4 import BeautifulSoup 
import requests

# Fetching the content of a webpage and parsing it with BeautifulSoup
# The URL is a test site for web scraping
url = "https://webscraper.io/test-sites/e-commerce/scroll"

#Sending a GET request to the URL 
#This returns a response object
request = requests.get(url)


soup = BeautifulSoup(request.content,"html.parser")
cards = soup.select('div.product-wrapper')
for card in cards:
    image = card.select_one('.img-fluid').get('src')
    price = card.select_one('span').text
    #The strip is used to remove any leading or trailing whitespace
    title = card.select_one('a.title').text.strip()
    description = card.select_one('p.description').text
    rating = card.select_one('p.review-count &amp;gt; span').text

    print(f"Title: {title}")
    print(f"Description: {description}")
    print(f"Price: {price}")
    print(f"Rating: {rating}")
    print(f"Image URL: {image}")
    print("-" * 40)

Title: Apple MacBook...
Description: Apple MacBook Air 13", Intel Core i5 1.8GHz, 8GB, 256GB SSD, Intel HD 6000, RUS
Price: $1347.78
Rating: 11
Image URL: /images/test-sites/e-commerce/items/cart2.png
----------------------------------------
Title: HP 250 G3
Description: 15.6", Core i5-4210U, 4GB, 500GB, Windows 8.1
Price: $520.99
Rating: 13
Image URL: /images/test-sites/e-commerce/items/cart2.png
----------------------------------------
Title: Lenovo ThinkPa...
Description: Lenovo ThinkPad Yoga 370 Black, 13.3" FHD IPS Touch, Core i5-7200U, 8GB, 256GB SSD, 4G, Windows 10 Pro
Price: $1362.24
Rating: 12
Image URL: /images/test-sites/e-commerce/items/cart2.png
----------------------------------------

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

&lt;/div&gt;



</description>
      <category>html</category>
      <category>tutorial</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>First Steps Into Web Scraping</title>
      <dc:creator>Nelson Orina</dc:creator>
      <pubDate>Sat, 27 Sep 2025 05:05:09 +0000</pubDate>
      <link>https://dev.to/nelson_orina_a538ba52e9ed/first-steps-into-web-scraping-gde</link>
      <guid>https://dev.to/nelson_orina_a538ba52e9ed/first-steps-into-web-scraping-gde</guid>
      <description>&lt;p&gt;&lt;strong&gt;1. Setting up Environment&lt;/strong&gt;&lt;br&gt;
In order to start web scraping with python you first have to set up your development environment. &lt;/p&gt;

&lt;p&gt;Firstly, navigate to your project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd path/to/your/project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and run the command &lt;strong&gt;python -m venv venv&lt;/strong&gt; . This creates a virtual environment in your project's root directory called venv. &lt;/p&gt;

&lt;p&gt;Secondly, activate the created virtual environment by running the command &lt;strong&gt;source venv/bin/activate&lt;/strong&gt; on mac or linux. &lt;br&gt;
For windows run the command &lt;strong&gt;source venv/Scripts/activate&lt;/strong&gt; for GitBash&lt;br&gt;
Command Prompt: venv\Scripts\activate.bat&lt;br&gt;
PowerShell: venv\Scripts\Activate.ps1&lt;/p&gt;

&lt;p&gt;A virtual environment isolates your projects python dependencies from the rest of your system. This means you can have multiple projects in the same machine, each with its own python version and library version without conflict. This also makes it easier to share your projects with others by listing all your dependencies on a requirements.txt file &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Flo06d19cz11w7yoslklb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flo06d19cz11w7yoslklb.png" alt=" " width="722" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you activate your virtual environment, your terminal prompt will change. You’ll see the name of the virtual environment in parentheses before your username, for example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(venv) user@User:~/Personal/WebScraping$&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;This is a clear sign that your project is running inside its own isolated Python environment. From here, any packages you install (like requests or beautifulsoup4) will only be available inside this project, not system wide which is exactly what we want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Installing Necessary Packages&lt;/strong&gt;&lt;br&gt;
Now that our virtual environment is active, we can now install the necessary packages we need for web scraping. &lt;/p&gt;

&lt;p&gt;The most common libraries used for beginners is: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Requests&lt;/strong&gt; -&amp;gt; used to send HTTP requests and download web pages. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;beautifulsoup4&lt;/strong&gt; -&amp;gt; a powerful tool that works hand-in-hand with a html parser. It takes the structured data created from the raw HTML and gives you a simple way to search, navigate and extract information from it. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can install this libraries by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install requests beautifulsoup4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installation is done, it's a good idea to save you dependencies in a file called requirements.txt. This makes it easy to recreate the same environment later or share it with others.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip freeze &amp;gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your requirements.txt will list all the installed packages. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 Writing your First Scraper&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is a simple Python web scraper that fetches a web page and prints its title.&lt;/p&gt;

&lt;p&gt;This example is great for beginners because it shows the entire scraping flow in just a few lines of code:&lt;/p&gt;

&lt;p&gt;1.Send an HTTP request to download a page.&lt;/p&gt;

&lt;p&gt;2.Parse the page’s HTML with BeautifulSoup.&lt;/p&gt;

&lt;p&gt;3.Extract a specific element (the h2 tag in this case).&lt;/p&gt;

&lt;p&gt;4.Print the result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Import the BeautifulSoup class from the bs4 library
from bs4 import BeautifulSoup  

# Import the requests module to send HTTP requests
import requests  

# Define the target URL (this is a demo e-commerce site for practicing web scraping)
url = "https://webscraper.io/test-sites/e-commerce/scroll"

# Send a GET request to the URL
# This will download the HTML content of the page and return a response object
response = requests.get(url)

# Parse the HTML content of the page using BeautifulSoup
# The "html.parser" is a built-in parser that converts the raw HTML into a navigable tree
soup = BeautifulSoup(response.content, "html.parser")

# Find the first &amp;lt;h2&amp;gt; element on the page and extract its text content
# .text gets only the text between the tags, without the HTML tags themselves
title = soup.find('h2').text

# Print the result to confirm that everything worked correctly
print("Title:", title)

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

&lt;/div&gt;



&lt;p&gt;Here is the expected output of the above script:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fskokehrhp3snlcfwds77.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fskokehrhp3snlcfwds77.png" alt=" " width="689" height="81"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4.&lt;strong&gt;Important: Web Scraping Best Practices&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before we continue, it's crucial to understand the legal and ethical aspects of web scraping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always check &lt;code&gt;robots.txt&lt;/code&gt; (e.g., &lt;code&gt;https://website.com/robots.txt&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Respect rate limits - don't overwhelm servers&lt;/li&gt;
&lt;li&gt;Check the website's Terms of Service&lt;/li&gt;
&lt;li&gt;Use scraping for legitimate purposes only&lt;/li&gt;
&lt;li&gt;Consider using official APIs when available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5.&lt;strong&gt;Recap&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;In this tutorial, you've successfully:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up an isolated Python development environment using virtual environments. &lt;/li&gt;
&lt;li&gt;Installed essential web scraping libraries (requests and beautifulsoup4)&lt;/li&gt;
&lt;li&gt;Written your first functional web scraper that extracts data from a live website&lt;/li&gt;
&lt;li&gt;Learned crucial best practices for ethical and responsible scraping.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While requests and BeautifulSoup are perfect for learning the fundamentals, modern web scraping often requires more advanced tools. In upcoming posts, we'll explore Playwright for handling JavaScript-heavy sites and n8n for building complete automation workflows without code.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
    </item>
  </channel>
</rss>
