Laravel

Laravel is a powerful and popular PHP framework that simplifies and accelerates web application development. Its elegant syntax, robust features like Eloquent ORM, Blade templating, and built-in security tools help developers create efficient and scalable apps. With strong community support and extensive documentation, Laravel is an ideal choice for both novice and experienced developers

Laravel Query Builder vs. Eloquent: A Comprehensive Comparison

19 May 2025 | Category:

Laravel provides two powerful tools for interacting with databases: Query Builder and Eloquent ORM. Both allow you to query and manipulate data, but they differ in their approach, syntax, and use cases. This SEO-friendly, plagiarism-free guide compares Laravel’s Query Builder and Eloquent ORM, highlighting their features, differences, and when to use each. Based on Laravel 11 (as of May 19, 2025), this tutorial builds on previous discussions about models and migrations and is designed for beginners and intermediate developers.


What is Query Builder?

Laravel’s Query Builder is a fluent, database-agnostic interface for constructing and executing SQL queries. It provides methods to build queries programmatically without writing raw SQL, using the DB facade. Query Builder operates directly on tables and returns results as arrays or collections.

Key Features

  • Fluent Interface: Chain methods to build queries (e.g., where(), select(), join()).
  • Database Agnostic: Works across MySQL, PostgreSQL, SQLite, and SQL Server.
  • Raw SQL Support: Allows raw SQL for complex queries.
  • Lightweight: No overhead of ORM features like models or relationships.
  • Returns Collections/Arrays: Results are typically Illuminate\Support\Collection or plain arrays.

What is Eloquent ORM?

Eloquent ORM (Object-Relational Mapping) is Laravel’s active record implementation, where each model class maps to a database table, and instances represent table rows. Eloquent builds on Query Builder, adding object-oriented features like relationships, mutators, and mass assignment.

Key Features

  • Active Record: Models represent table rows with attributes matching columns.
  • Relationships: Define one-to-one, one-to-many, or many-to-many relationships.
  • Mass Assignment: Safely insert/update multiple fields.
  • Timestamps and Soft Deletes: Automatic handling of created_at, updated_at, and deleted_at.
  • Object-Oriented: Returns model instances for intuitive data manipulation.

Key Differences Between Query Builder and Eloquent

FeatureQuery BuilderEloquent ORM
ApproachProcedural, query-focusedObject-oriented, model-focused
SyntaxUses DB facade (e.g., DB::table())Uses model classes (e.g., Post::where())
ResultsArrays or CollectionsModel instances or Collections
RelationshipsManual joins requiredBuilt-in relationship methods
Mass AssignmentNot supportedSupported via $fillable or $guarded
TimestampsManual handlingAutomatic if enabled
Soft DeletesManual implementationBuilt-in with SoftDeletes trait
PerformanceLightweight, faster for simple queriesSlightly heavier due to ORM overhead
ComplexitySimpler for raw queriesMore features for complex applications
Use CaseSimple queries, non-model tablesModel-based apps with relationships

Query Builder in Action

Query Builder is accessed via the DB facade and operates directly on table names. It’s ideal for simple queries or when you don’t need model overhead.

Example: Querying Posts

use Illuminate\Support\Facades\DB;

// Get all published posts
$posts = DB::table('posts')
    ->where('is_published', true)
    ->orderBy('title')
    ->get();

// Get a single post by ID
$post = DB::table('posts')
    ->where('id', 1)
    ->first();

// Insert a post
DB::table('posts')->insert([
    'title' => 'New Post',
    'slug' => 'new-post',
    'content' => 'Content here',
    'is_published' => false,
    'user_id' => 1,
    'created_at' => now(),
    'updated_at' => now(),
]);

// Update posts
DB::table('posts')
    ->where('user_id', 1)
    ->update(['is_published' => true]);

// Delete posts
DB::table('posts')
    ->where('id', 1)
    ->delete();

// Join with users table
$posts = DB::table('posts')
    ->join('users', 'posts.user_id', '=', 'users.id')
    ->select('posts.title', 'users.name')
    ->get();
  • Output: $posts is a Collection of stdClass objects or arrays.
  • Note: You must manually handle timestamps and relationships (e.g., via joins).

Advanced Query Builder Features

  • Aggregates:
  $count = DB::table('posts')->count();
  $maxViews = DB::table('posts')->max('views');
  • Raw Queries:
  $posts = DB::table('posts')
      ->whereRaw('LENGTH(title) > ?', [10])
      ->get();
  • Subqueries:
  $posts = DB::table('posts')
      ->whereIn('user_id', function ($query) {
          $query->select('id')
                ->from('users')
                ->where('active', true);
      })
      ->get();

Eloquent ORM in Action

Eloquent uses model classes to interact with tables, providing an object-oriented interface. It’s ideal for applications with complex relationships or model-specific logic.

Example: Querying Posts

Assume a Post model (app/Models/Post.php):

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use SoftDeletes;

    protected $fillable = ['title', 'slug', 'content', 'is_published', 'user_id'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

CRUD Operations:

use App\Models\Post;

// Get all published posts
$posts = Post::where('is_published', true)->orderBy('title')->get();

// Get a single post by ID
$post = Post::find(1); // Or Post::findOrFail(1);

// Create a post
$post = Post::create([
    'title' => 'New Post',
    'slug' => 'new-post',
    'content' => 'Content here',
    'is_published' => false,
    'user_id' => 1,
]);

// Update a post
$post = Post::find(1);
$post->update(['title' => 'Updated Title']);

// Delete a post (soft delete)
$post->delete();

// Restore soft-deleted post
Post::withTrashed()->find(1)->restore();

// Eager load relationship
$posts = Post::with('user')->get();
foreach ($posts as $post) {
    echo $post->title . ' by ' . $post->user->name;
}
  • Output: $posts is a Collection of Post model instances.
  • Note: Timestamps are automatic, and relationships are accessed via methods.

Advanced Eloquent Features

  • Relationships:
  $userPosts = User::find(1)->posts()->where('is_published', true)->get();
  • Accessors:
  public function getTitleAttribute($value)
  {
      return strtoupper($value);
  }
  • Scopes:
  public function scopePublished($query)
  {
      return $query->where('is_published', true);
  }
  // Usage
  $posts = Post::published()->get();
  • Soft Deletes:
  $trashed = Post::onlyTrashed()->get();

Query Builder vs. Eloquent: Detailed Comparison

1. Syntax and Readability

  • Query Builder:
  • Procedural, table-focused: DB::table('posts')->where('id', 1).
  • More verbose for complex queries involving joins.
  • Clear for raw SQL or simple queries.
  • Eloquent:
  • Object-oriented: Post::find(1).
  • Cleaner for relationship-based queries.
  • More intuitive for developers familiar with ORM.

Example (Get published posts by a user):

  • Query Builder:
  $posts = DB::table('posts')
      ->join('users', 'posts.user_id', '=', 'users.id')
      ->where('posts.is_published', true)
      ->where('users.id', 1)
      ->select('posts.*')
      ->get();
  • Eloquent:
  $posts = Post::where('is_published', true)
      ->where('user_id', 1)
      ->with('user')
      ->get();

2. Performance

  • Query Builder:
  • Lightweight, no ORM overhead (e.g., no model instantiation).
  • Faster for simple queries or bulk operations.
  • Ideal for raw SQL or complex joins.
  • Eloquent:
  • Slightly slower due to model instantiation, relationship handling, and event dispatching.
  • Can cause N+1 query issues if relationships aren’t eager-loaded.
  • Optimized with techniques like with() or caching.

Example (N+1 Issue in Eloquent):

// Bad: N+1 queries
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->user->name; // Queries user for each post
}

// Good: Eager loading
$posts = Post::with('user')->get();
foreach ($posts as $post) {
    echo $post->user->name; // Single query for users
}

3. Relationships

  • Query Builder:
  • Requires manual joins:
    php $posts = DB::table('posts') ->join('users', 'posts.user_id', '=', 'users.id') ->select('posts.title', 'users.name') ->get();
  • More control but verbose for complex relationships.
  • Eloquent:
  • Built-in relationship methods:
    php $posts = Post::with('user')->get();
  • Simplifies one-to-many, many-to-many, etc.

4. Maintainability

  • Query Builder:
  • Better for ad-hoc queries or scripts.
  • Less maintainable for large applications with complex logic.
  • No built-in support for model-specific features like accessors or scopes.
  • Eloquent:
  • Ideal for maintainable, model-driven applications.
  • Encapsulates table logic in models (e.g., scopes, relationships).
  • Easier to extend with custom methods or traits.

5. Error Handling

  • Query Builder:
  • Returns null or empty collections for missing records.
  • Manual validation for data integrity.
  • Eloquent:
  • Methods like findOrFail() throw ModelNotFoundException.
  • Built-in validation via mass assignment ($fillable).

When to Use Query Builder

  • Simple Queries: Fetch data from tables without needing models (e.g., reporting or analytics).
  • Non-Model Tables: Query tables without corresponding models (e.g., pivot tables, logs).
  • Performance-Critical Operations: Bulk inserts, updates, or complex joins where ORM overhead is undesirable.
  • Raw SQL Needs: Use raw queries for database-specific features not supported by Eloquent.
  • Scripts or Migrations: Quick queries in seeders or maintenance scripts.

Example Use Case: Aggregate report from a logs table:

$stats = DB::table('logs')
    ->select(DB::raw('DATE(created_at) as date, COUNT(*) as count'))
    ->groupBy('date')
    ->get();

When to Use Eloquent

  • Model-Driven Applications: Apps with clear entities (e.g., users, posts, comments).
  • Relationships: Projects requiring one-to-many, many-to-many, or polymorphic relationships.
  • CRUD Operations: Standard create, read, update, delete tasks with model logic.
  • Soft Deletes and Timestamps: Automatic handling of deleted_at or created_at.
  • Maintainable Code: Large applications where model encapsulation improves readability.

Example Use Case: Blog with posts and users:

$posts = Post::with('user')
    ->where('is_published', true)
    ->orderBy('published_at', 'desc')
    ->paginate(10);

Combining Query Builder and Eloquent

You can mix Query Builder and Eloquent when needed, as Eloquent is built on top of Query Builder.

Example: Use Query Builder within an Eloquent model:

class Post extends Model
{
    public function scopeComplexQuery($query)
    {
        return $query->whereIn('id', function ($subQuery) {
            $subQuery->select('post_id')
                     ->from('post_tag')
                     ->where('tag_id', 1);
        });
    }
}

// Usage
$posts = Post::complexQuery()->get();

Example: Use Eloquent model with raw Query Builder:

$posts = DB::table('posts')
    ->where('is_published', true)
    ->get()
    ->map(fn ($post) => Post::make((array) $post));

Best Practices

  1. Choose Based on Needs:
  • Use Query Builder for simple, performance-critical, or non-model queries.
  • Use Eloquent for model-driven apps with relationships.
  1. Avoid N+1 Issues in Eloquent:
  • Always eager-load relationships with with():
    php Post::with(['user', 'tags'])->get();
  1. Secure Queries:
  • Use parameter binding to prevent SQL injection:
    php DB::table('posts')->where('title', '=', $title)->get();
  • Use $fillable in Eloquent for mass assignment.
  1. Optimize Performance:
  • Cache queries with Laravel’s cache system for frequently accessed data.
  • Use select() to retrieve only needed columns:
    php Post::select(['id', 'title'])->get();
  1. Debug Queries:
  • Use toSql() or query logging:
    php echo Post::where('is_published', true)->toSql(); DB::enableQueryLog(); dd(DB::getQueryLog());
  1. Keep Models Clean:
  • Move complex query logic to repositories or query scopes in Eloquent.
  1. Test Queries:
  • Test in a local environment with php artisan tinker or unit tests.

Debugging Query Builder and Eloquent

  • Query Errors:
  • Check .env database settings and test with DB::connection()->getPdo().
  • Verify table/column names in Query Builder or model configuration.
  • Eloquent Model Issues:
  • Ensure $table, $primaryKey, and $fillable are correct.
  • Check relationship foreign keys (e.g., user_id exists).
  • Performance Bottlenecks:
  • Use Laravel Debugbar or query logging to identify slow queries.
  • Optimize with indexes or eager loading.
  • Logs:
  • Check storage/logs/laravel.log for errors.

Example: Query Builder vs. Eloquent

Scenario: Fetch published posts with user names

Database:

  • posts: id, title, content, is_published, user_id, created_at, updated_at.
  • users: id, name, email.

Query Builder:

$posts = DB::table('posts')
    ->join('users', 'posts.user_id', '=', 'users.id')
    ->where('posts.is_published', true)
    ->select('posts.title', 'posts.content', 'users.name as user_name')
    ->orderBy('posts.created_at', 'desc')
    ->get();

// Access data
foreach ($posts as $post) {
    echo "{$post->title} by {$post->user_name}";
}

Eloquent:

// Post model
class Post extends Model
{
    protected $fillable = ['title', 'content', 'is_published', 'user_id'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function scopePublished($query)
    {
        return $query->where('is_published', true);
    }
}

// Query
$posts = Post::published()
    ->with('user')
    ->orderBy('created_at', 'desc')
    ->get();

// Access data
foreach ($posts as $post) {
    echo "{$post->title} by {$post->user->name}";
}

Comparison:

  • Query Builder: More verbose for joins, returns plain objects, manual timestamp handling.
  • Eloquent: Cleaner syntax, model instances, automatic relationships, reusable scopes.

Conclusion

Laravel’s Query Builder and Eloquent ORM are both powerful tools for database interactions, but they serve different purposes. Query Builder is lightweight and ideal for simple, performance-critical, or non-model queries, while Eloquent excels in model-driven applications with relationships and complex logic. By understanding their strengths and combining them when needed, you can build efficient and maintainable Laravel applications.

Next Steps:

  • Experiment with Query Builder: DB::table('posts')->get().
  • Create an Eloquent model and define relationships.
  • Test queries in php artisan tinker.

For deeper insights, explore Laravel’s official documentation or connect with the Laravel community on platforms like X. Start querying your database effectively today!

Laravel Query Builder vs Eloquent Example

This artifact demonstrates fetching published posts with user names using both Query Builder and Eloquent.

Migration (database/migrations/2025_05_19_123456_create_posts_table.php)

<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title', 255);
            $table->text('content');
            $table->boolean('is_published')->default(false);
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

Model (app/Models/Post.php)

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = ['title', 'content', 'is_published', 'user_id'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function scopePublished($query)
    {
        return $query->where('is_published', true);
    }
}

Query Builder Example

use Illuminate\Support\Facades\DB;

$posts = DB::table('posts')
    ->join('users', 'posts.user_id', '=', 'users.id')
    ->where('posts.is_published', true)
    ->select('posts.title', 'posts.content', 'users.name as user_name')
    ->orderBy('posts.created_at', 'desc')
    ->get();

foreach ($posts as $post) {
    echo "{$post->title} by {$post->user_name}\n";
}

Eloquent Example

use App\Models\Post;

$posts = Post::published()
    ->with('user')
    ->orderBy('created_at', 'desc')
    ->get();

foreach ($posts as $post) {
    echo "{$post->title} by {$post->user->name}\n";
}

Usage

  1. Ensure your .env file is configured and the users table exists.
  2. Save the migration and model files.
  3. Run the migration:
   php artisan migrate
  1. Seed sample data (e.g., using a factory or seeder).
  2. Test both queries in a controller or php artisan tinker.

This example highlights the procedural, table-focused approach of Query Builder versus the object-oriented, model-driven approach of Eloquent, showcasing their syntax and handling of relationships.