Hello! Let's get started with today's lesson.
In our last session, we meticulously designed a normalized database schema for a simple e-commerce application. We moved from raw requirements to a logical blueprint, ensuring our data structure was efficient and free from redundancy by applying the principles of normalization up to 3NF.
Today, we transition from blueprint to construction. You will learn how to create and manage that database schema using Laravel migrations. Migrations are one of Laravel's most powerful features, acting as version control for your database. They allow you and your team to define, modify, and share the application's database schema programmatically. By the end of this lesson, you will have translated the schema we designed into actual tables within your database.
1. What Are Migrations and Why Use Them?
Before we write any code, it's crucial to understand the "why" behind migrations. You could, of course, open a database management tool and create your tables manually. However, this approach quickly becomes unmanageable, especially in a team environment.
Migrations solve this by codifying your schema changes in PHP files. This means:
- Collaboration is seamless: A teammate can pull your latest code, run one command, and have their database schema perfectly match yours.
- History is tracked: Just like Git tracks code changes, migrations track your database's evolution.
- Deployment is reliable: You can run migrations as part of an automated deployment process to update your production database schema consistently.

To get a feel for how this works in practice, let's watch a short introduction.
30 Days to Learn Laravel, Ep 08 - Introduction to Migrations
This video from Laracasts provides a great initial overview of what migrations are, how they are generated, and how they are run.
Watch the sections that explain the purpose of migrations (00:29 - 00:47) and the structure of a migration file (08:23 - 10:59). Pay attention to the concept of up() and down() methods and the idea of defining your schema in PHP.
The key takeaway is that migrations provide a robust and shareable way to manage your database structure. Not using them is a common mistake for developers new to the framework.
Top 5 Mistakes in Laravel DB Migrations
The Laravel Daily channel highlights why skipping migrations is a critical error, especially when thinking about teamwork and different server environments.
Watch the first point in this video (00:54 - 02:40) to reinforce the importance of using migrations as a form of version control for your database.
2. Setting Up Your MSSQL Connection
Before we can run a migration, Laravel needs to know how to connect to your database. Since your goal is to master Laravel with MSSQL, we'll configure that connection now.
First, you'll need to update your .env file with the correct credentials for your local MSSQL server. The following article shows exactly which variables to change.
Setting up a Laravel project with SQL Server
This article from dev.to provides a straightforward guide to connecting a Laravel project to SQL Server. We'll focus on the configuration part.
Read the first two parts of the article. First, update config/database.php to set the default connection to sqlsrv. Second, and more importantly, update your .env file with your MSSQL server details (DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD).
As the article mentions, running php artisan migrate after this change might result in a could not find driver error. This happens if the required PHP extensions for SQL Server (pdo_sqlsrv and sqlsrv) are not enabled in your php.ini file. The same article guides you through enabling them. Ensure your environment is correctly configured before proceeding.
3. Creating Your First Migrations
With the connection configured, let's start building the tables from our e-commerce schema.
Generating a Migration File
We use an Artisan command to create a new migration file. Laravel's command-line tool is smart; if you follow its naming conventions, it will generate boilerplate code for you.
Open your terminal in the project root and run the following command to create a migration for our products table:
php artisan make:migration create_products_table
Laravel uses the name create_products_table to infer that we want to create a table named products. Now, open the new file located in the database/migrations/ directory. It will have a timestamp in its name, like YYYY_MM_DD_HHMMSS_create_products_table.php.
You'll see a structure similar to this:
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('products', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('products');
}
};
up()method: This is executed when you runphp artisan migrate. Its job is to add new tables, columns, or indexes to your database.down()method: This is executed when you roll back a migration. It should do the exact opposite of theup()method. Here, it drops the table thatup()created.
Defining the Schema
Inside the up() method's closure, we use the $table object (an instance of Illuminate\Database\Schema\Blueprint) to define the table's columns. Laravel's Schema Builder provides a rich, database-agnostic API for this.
Let's flesh out the products table migration based on the schema we designed in the last lesson.
// In database/migrations/YYYY_MM_DD_HHMMSS_create_products_table.php
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id(); // BigInt, Unsigned, Auto-incrementing Primary Key
$table->string('title');
$table->text('description')->nullable(); // Good practice to allow nullable descriptions
$table->decimal('price', 8, 2); // 8 total digits, 2 after the decimal
$table->unsignedInteger('stock_quantity')->default(0);
$table->timestamps(); // Creates created_at and updated_at columns
});
}
The official Laravel documentation is your best friend for discovering all available column types and modifiers.
Database: Migrations - Laravel Documentation
The official documentation is the definitive reference for all schema-building capabilities. It's a page worth bookmarking.
Skim through the 'Available Column Types' section to see the variety of methods available (e.g., string, integer, text, decimal). Then, look at the 'Column Modifiers' table to understand helpers like ->nullable(), ->default(), and ->unsigned().
Your Turn: Create the Other Tables
Now it's your turn to apply what you've learned. Create the migrations for the users, categories, and orders tables based on our schema design.
-
Generate the migration files:
php artisan make:migration create_users_table php artisan make:migration create_categories_table php artisan make:migration create_orders_table php artisan make:migration create_order_product_table php artisan make:migration create_category_product_table -
Define the schema in the
up()method of each new migration file. Use the schema from the previous lesson as your guide. I've provided the code forusersandordersbelow to help you. Try to completecategoriesand the two pivot tables (order_product,category_product) on your own.create_users_table.php:Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->rememberToken(); // A helper for "remember me" functionality $table->timestamps(); });create_orders_table.php:Schema::create('orders', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('user_id'); // We'll add the foreign key constraint in the next lesson $table->decimal('total_amount', 10, 2); $table->string('status')->default('pending'); $table->timestamps(); });Challenge: Fill in the
up()methods forcreate_categories_table,create_order_product_table, andcreate_category_product_table.
4. Running and Managing Migrations
Once you've defined your migrations, you need to execute them. Artisan provides several commands for this.
30 Days to Learn Laravel, Ep 08 - Introduction to Migrations
Let's revisit the Laracasts video to see the migration commands in action.
Watch the demonstrations of php artisan migrate (04:16 - 05:53) and php artisan migrate:fresh (10:59 - 12:50). Note the difference: migrate runs pending migrations, while migrate:fresh drops all tables and starts over.
Here are the most common commands:
-
php artisan migrate
This command runs theup()method on all migrations that haven't been run yet. Run this now to create your tables in the MSSQL database. -
php artisan migrate:status
Shows a list of all migrations and whether they have been run. -
php artisan migrate:rollback
This rolls back the last batch of migrations that were run. It executes thedown()method for each of them. -
php artisan migrate:fresh
This command is extremely useful during development. It drops all tables from your database and then runs all migrations from the beginning. Warning: Never run this in a production environment with live data!
5. The Golden Rule: Don't Modify Pushed Migrations
A migration file is a record of a change at a point in time. Once a migration has been committed to version control and shared with your team (or deployed), you must not edit it.
Why? If you change an existing migration that your teammate has already run, their migrate command will report "nothing to migrate," but their schema will be out of sync with yours, leading to bugs.
The correct approach is to create a new migration to make the change.
- Wrong: Edit
create_products_tableto add askucolumn. - Right: Run
php artisan make:migration add_sku_to_products_tableand add the new column in that new file.
// In database/migrations/YYYY_MM_DD_HHMMSS_add_sku_to_products_table.php
public function up(): void
{
Schema::table('products', function (Blueprint $table) {
$table->string('sku')->unique()->after('id');
});
}
public function down(): void
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('sku');
});
}
Notice we use Schema::table() to modify an existing table.
This workflow is one of the most important professional practices when working with migrations.
Top 5 Mistakes in Laravel DB Migrations
Let's see an explanation of why modifying existing migrations is a problem and what to do instead.
Watch the section 'modifying existing migrations' (02:40 - 05:18). This clearly demonstrates the problem and the correct solution of creating a new migration for schema changes.
Conclusion
Congratulations! You've taken the logical schema you designed and turned it into a physical database structure using Laravel migrations. You now have a repeatable, version-controlled process for managing your database schema.
Key Takeaways:
- Migrations act as version control for your database, making team collaboration and deployments safe and predictable.
- The
up()method applies schema changes, while thedown()method reverses them. - The
php artisan make:migrationcommand is used to generate migration files. - Laravel's Schema Builder provides a fluent, database-agnostic API for defining table structures.
- The commands
migrate,rollback, andfreshare your primary tools for managing the schema state during development. - The golden rule: Once a migration is shared, create a new migration to make further changes rather than editing the old one.
Next Up:
Our tables exist, but they are just isolated islands of data. They don't know about each other yet. In the next lesson, we will complete our schema implementation by learning how to define foreign key constraints and indexes within migrations. This will enforce the relationships we designed (like orders belonging to users) at the database level and prepare our tables for efficient querying.