DEV Community

Vincent Tommi
Vincent Tommi

Posted on

Django vs Laravel: Understanding Models and Database Migrations

As I started working more seriously with Laravel after spending a significant amount of time working with Django, I noticed an important difference in how the two frameworks approach database schema management.

Coming from Django, I was used to a workflow like:

python manage.py makemigrations
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

In Django, I normally start by creating or modifying a model. Django then detects the changes and generates migration files for me.

When I started working with Laravel, I initially expected a similar workflow.

However, Laravel made me think about database migrations differently.

The biggest lesson I have learned is this:

Django models and Laravel migrations play different roles in the database development workflow.

Let's compare them.


1. The Django Approach

In Django, we normally define our database structure using models.

For example:

from django.db import models


class MenuItem(models.Model):
    name = models.CharField(max_length=255)
    code = models.CharField(max_length=100, unique=True)
    price = models.DecimalField(max_digits=15, decimal_places=2)
    is_active = models.BooleanField(default=True)

    def __str__(self):
        return self.name
Enter fullscreen mode Exit fullscreen mode

After creating or modifying the model, we run:

python manage.py makemigrations
Enter fullscreen mode Exit fullscreen mode

Django examines the changes to the models and generates a migration file.

Then we apply the migration:

python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

So the typical Django workflow is:

Modify Model
     ↓
makemigrations
     ↓
Migration File Generated
     ↓
migrate
     ↓
Database Updated
Enter fullscreen mode Exit fullscreen mode

This means Django provides a strong relationship between the model definition and the generated migration.


2. The Laravel Approach

Laravel uses Eloquent as its ORM, but database schema management is handled explicitly through migrations.

For example, suppose I want to create a menu_items table.

I can create a migration:

php artisan make:migration create_menu_items_table
Enter fullscreen mode Exit fullscreen mode

Laravel generates a migration file in:

database/migrations/
Enter fullscreen mode Exit fullscreen mode

I can then define the table:

Schema::create('menu_items', function (Blueprint $table) {
    $table->id();

    $table->string('name');
    $table->string('code')->unique();
    $table->decimal('price', 15, 2)->default(0);
    $table->boolean('is_active')->default(true);

    $table->timestamps();
});
Enter fullscreen mode Exit fullscreen mode

Then I run:

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Laravel executes the migration and creates the table in the database.

The workflow is therefore more like:

Create/Edit Migration
        ↓
Define Database Schema
        ↓
php artisan migrate
        ↓
Database Updated
Enter fullscreen mode Exit fullscreen mode

3. Where Does the Laravel Model Come In?

This was one of the things that initially confused me coming from Django.

In Laravel, I can create a model using:

php artisan make:model MenuItem
Enter fullscreen mode Exit fullscreen mode

This creates something like:

app/Models/MenuItem.php
Enter fullscreen mode Exit fullscreen mode

The model can contain:

class MenuItem extends Model
{
    protected $fillable = [
        'name',
        'code',
        'price',
        'is_active',
    ];

    protected $casts = [
        'price' => 'decimal:2',
        'is_active' => 'boolean',
    ];
}
Enter fullscreen mode Exit fullscreen mode

The model is used by Eloquent to interact with the database.

For example:

$menuItem = MenuItem::create([
    'name' => 'Chicken Burger',
    'code' => 'BURGER-001',
    'price' => 750,
    'is_active' => true,
]);
Enter fullscreen mode Exit fullscreen mode

The important distinction is that this model does not automatically generate or modify the database schema when I change its properties.

The migration is responsible for defining the database structure.


4. Laravel Can Create Both at Once

Laravel also provides a convenient command for creating a model and its migration together:

php artisan make:model MenuItem -m
Enter fullscreen mode Exit fullscreen mode

This creates:

app/Models/MenuItem.php
Enter fullscreen mode Exit fullscreen mode

and a migration under:

database/migrations/
Enter fullscreen mode Exit fullscreen mode

This is a useful workflow because I can create the model and migration at the same time.

However, they still have different responsibilities.

Model

The model defines how my application interacts with the data.

Migration

The migration defines how the database structure is created or changed.


5. Django vs Laravel: The Key Difference

This is where my initial understanding needed some correction.

It is not quite accurate to say:

"In Laravel you create the migration first, while in Django you create the model first."

Both frameworks allow different workflows.

The more accurate distinction is:

Django's migration system is largely model-driven, while Laravel's migration system is explicitly migration-driven.

In Django:

models.py
    ↓
makemigrations
    ↓
migrations/
    ↓
migrate
    ↓
Database
Enter fullscreen mode Exit fullscreen mode

In Laravel:

Migration
    ↓
php artisan migrate
    ↓
Database

Model
    ↓
Eloquent
    ↓
Application ↔ Database
Enter fullscreen mode Exit fullscreen mode

The Laravel model and migration work together, but changing the model does not automatically generate a migration.


6. Example: Adding a Column

Let's say I already have a menu_items table and want to add an image column.

Django

I would modify the model:

class MenuItem(models.Model):
    name = models.CharField(max_length=255)
    image = models.ImageField(upload_to='menu/', null=True, blank=True)
Enter fullscreen mode Exit fullscreen mode

Then:

python manage.py makemigrations
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Django detects that image was added and creates the appropriate migration.


Laravel

In Laravel, I would create a new migration:

php artisan make:migration add_image_to_menu_items_table
Enter fullscreen mode Exit fullscreen mode

Then define the change:

Schema::table('menu_items', function (Blueprint $table) {
    $table->string('image')->nullable();
});
Enter fullscreen mode Exit fullscreen mode

Then:

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

The database is updated.

If I want to access the new field through my Laravel model, I may also update $fillable if I am using mass assignment:

protected $fillable = [
    'name',
    'code',
    'price',
    'image',
    'is_active',
];
Enter fullscreen mode Exit fullscreen mode

This demonstrates the difference clearly.


7. Another Important Difference: Migration Detection

This is probably the biggest difference I noticed while moving between Django and Laravel.

With Django, I can make a change to:

models.py
Enter fullscreen mode Exit fullscreen mode

and then run:

python manage.py makemigrations
Enter fullscreen mode Exit fullscreen mode

Django compares the current models against the previous migration state and generates a migration.

With Laravel, if I change something in my model, Laravel does not automatically create a migration for that database change.

For example, changing:

protected $fillable = [
    'name',
    'price',
];
Enter fullscreen mode Exit fullscreen mode

does not change the database.

Likewise, adding a property or changing model configuration does not automatically alter the table.

I need to create a migration when the database schema itself needs to change.


8. Relationships Are Also Different

Both Django and Laravel provide excellent support for database relationships, but the syntax is different.

For example, suppose a MenuItem belongs to a MenuCategory.

Django

I might write:

class MenuItem(models.Model):
    menu_category = models.ForeignKey(
        MenuCategory,
        on_delete=models.CASCADE,
        related_name='menu_items'
    )
Enter fullscreen mode Exit fullscreen mode

Django then uses this relationship to provide database and ORM behavior.


Laravel

The migration defines the foreign key:

$table->foreignId('menu_category_id')
    ->constrained('menu_categories')
    ->cascadeOnDelete();
Enter fullscreen mode Exit fullscreen mode

Then the model defines the Eloquent relationship:

public function menuCategory(): BelongsTo
{
    return $this->belongsTo(MenuCategory::class);
}
Enter fullscreen mode Exit fullscreen mode

And the reverse relationship can be defined in MenuCategory:

public function menuItems(): HasMany
{
    return $this->hasMany(MenuItem::class);
}
Enter fullscreen mode Exit fullscreen mode

This separation helped me understand Laravel better:

Migration
    ↓
Database structure

Model
    ↓
Application behavior + relationships
Enter fullscreen mode Exit fullscreen mode

9. The Commands Side by Side

Here is a simple comparison.

Task Django Laravel
Create model Define model in models.py php artisan make:model MenuItem
Create migration Usually generated php artisan make:migration ...
Generate migration from model changes makemigrations No direct equivalent
Apply migrations python manage.py migrate php artisan migrate
Roll back migration python manage.py migrate app previous_migration php artisan migrate:rollback
Create model + migration Define model, then makemigrations php artisan make:model MenuItem -m
ORM Django ORM Eloquent ORM

10. My Laravel Workflow

After learning this difference, the Laravel workflow I am following for my POS project is becoming clearer.

For example, when creating an ingredient table:

Step 1: Create the migration

php artisan make:migration create_ingredients_table
Enter fullscreen mode Exit fullscreen mode

Step 2: Define the database structure

Schema::create('ingredients', function (Blueprint $table) {
    $table->id();
    $table->string('name');

    $table->foreignId('base_unit_id')
        ->constrained('units')
        ->restrictOnDelete();

    $table->decimal('cost', 15, 2)->default(0);
    $table->decimal('reorder_level', 15, 3)->default(0);

    $table->timestamps();
});
Enter fullscreen mode Exit fullscreen mode

Step 3: Run the migration

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Step 4: Create the model

php artisan make:model Ingredient
Enter fullscreen mode Exit fullscreen mode

Step 5: Configure the model

class Ingredient extends Model
{
    protected $fillable = [
        'name',
        'base_unit_id',
        'cost',
        'reorder_level',
    ];

    protected $casts = [
        'cost' => 'decimal:2',
        'reorder_level' => 'decimal:3',
    ];
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Add relationships

public function baseUnit(): BelongsTo
{
    return $this->belongsTo(Unit::class, 'base_unit_id');
}
Enter fullscreen mode Exit fullscreen mode

Now the migration handles the database structure while the model handles Eloquent interaction.


11. What I Have Learned Moving from Django to Laravel

Coming from Django, my first instinct was to think:

Model → makemigrations → migrate
Enter fullscreen mode Exit fullscreen mode

Laravel made me think more explicitly about:

Migration → Database
Model → Application/ORM
Enter fullscreen mode Exit fullscreen mode

Neither approach is simply "better." They are different design philosophies and workflows.

Django gives developers a very convenient model-driven migration workflow where changes to models can be detected and converted into migrations.

Laravel gives developers explicit control over database schema changes through migration files while Eloquent models focus on interacting with that schema.

Understanding this difference has made Laravel migrations much easier for me to work with.


Conclusion

Moving from Django to Laravel is not just about learning different syntax.

It also means learning how each framework expects you to think about application architecture.

The most important distinction I have learned is:

In Django, models are the primary definition of your database structure, and migrations are generated from model changes. In Laravel, migrations explicitly define and change the database schema, while Eloquent models provide the application's interface to that database.

Once I understood this, Laravel's workflow started making much more sense.

For someone moving from Django to Laravel, I would recommend not trying to force the Django workflow into Laravel.

Instead, understand the responsibility of each component:

Django

Model
  ↓
makemigrations
  ↓
Migration
  ↓
Database
Enter fullscreen mode Exit fullscreen mode

versus:

Laravel

Migration
  ↓
Database
  ↑
Eloquent Model
  ↑
Application
Enter fullscreen mode Exit fullscreen mode

Learning this distinction has been one of the useful lessons in my transition from Django to Laravel.

Top comments (0)