Introduction
Real apps remember. Students, marks, orders — the data must survive after the browser closes, and that home is the database. Laravel connects to it with two partners you will use in every project: migrations, which are versioned blueprints that build your tables, and models, PHP classes that speak for those tables so your code says Student::all() instead of writing raw SQL.
Why blueprints instead of clicking around in a database tool? Because a migration is code: it travels with the project, every teammate (and the live server, LV.8) runs the same file and gets the identical table. In this lesson you will connect the app to MySQL, design the students table as a migration, bring it to life, and read and write rows through the Student model — Eloquent, Laravel's object way of talking to tables.
(Roman Urdu: Migration table ka naksha hai jo code mein rehta hai; Model us table ka numainda hai jo aap ki zubaan bolta hai.)
Key points
- Connection settings live in
.env:DB_CONNECTION=mysql,DB_DATABASE=school_app,DB_USERNAME,DB_PASSWORD. The app reads these at boot; change the file, change the database — no code edits. Create the empty database first (in phpMyAdmin or MySQL), Laravel fills it. - A migration is a table's biography in code:
php artisan make:migration create_students_tablecreates a file with two methods —up()builds,down()undoes. Laravel runs each migration once and remembers which ones ran. Schema::createdesigns the table:$table->id(),$table->string('name'),$table->integer('marks'),$table->timestamps()(created_at + updated_at, managed automatically).php artisan migrateis the construction day: it runs every pendingup()and reports each table built. Changed your mind?migrate:rollbackrunsdown()and the table is gone — cleanly.- A model represents one table:
php artisan make:model Studentcreatesapp/Models/Student.php. By convention the classStudentspeaks for the tablestudents— plural, lowercase. No configuration needed when you follow the naming.(Roman Urdu: Model ka naam akela (Student), table ka naam jama (students) — ye chhota sa usool Laravel khud jor deta hai.) - Eloquent reads like English:
Student::all()every row,Student::find(1)the row with id 1,Student::findOrFail(99)the row — or an honest 404 page when it does not exist. - Saving is two honest steps: set the fields on a new model, call
save()— Laravel writes the INSERT and even fills the timestamps for you.
Code examples
Example 1 — Connect + design + build: .env, migration, migrate
# .env — the app's private settings
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=school_app
DB_USERNAME=root
DB_PASSWORD=php artisan make:migration create_students_table// the migration file's up() method
public function up(): void
{
Schema::create('students', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('city');
$table->integer('marks');
$table->timestamps();
});
}php artisan migrateWhat it does: .env tells Laravel where the database lives. The migration is the students table drawn as code — an id, two text columns, one number column, timestamps. migrate reads every blueprint and builds the real tables in MySQL.
What you will see: the terminal confirms each build:
INFO Running migrations.
2026_01_01_000001_create_students_table .............. 42ms DONEOpen phpMyAdmin and the students table stands there, empty, with exactly the five columns you designed.
Example 2 — The Model speaks: save your first student
// app/Models/Student.php — Artisan's file, already enough
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Student extends Model
{
// One class, one table: Student <-> students
}// anywhere in your app (a controller, for now)
$student = new Student;
$student->name = 'Ali Raza';
$student->city = 'Sahiwal';
$student->marks = 88;
$student->save();What it does: The model class is nearly empty — the power comes from the Model it extends. Creating a record is plain object work: new model, set fields like properties, save(). Laravel translates that into an INSERT and fills id, created_at and updated_at itself.
What you will see: the students table now holds its first row:
id | name | city | marks | created_at | updated_at
1 | Ali Raza | Sahiwal | 88 | 2026-01-01 09:00:00 | 2026-01-01 09:00:00Example 3 — Reading back: all(), find(), findOrFail()
$students = Student::all();
foreach ($students as $student) {
echo $student->name . ' — ' . $student->marks . '<br>';
}
$one = Student::find(1);
echo 'First student: ' . $one->name;
$missing = Student::findOrFail(99);What it does: all() returns every row as a collection of Student objects — five students after you save Sara (92), Bilal (76), Hira (95) and Usman (64) the same way as Example 2. find(1) fetches one by id. findOrFail(99) goes hunting for a student who does not exist — and instead of returning a dangerous null, it stops with a clean 404.
What you will see: the list prints all five names with marks; First student: Ali Raza; and the last line never prints — the browser shows Laravel's 404 page, because student 99 does not exist and findOrFail refused to pretend otherwise.
Quick recap
.envholds the connection; a migration is the table's blueprint in code;php artisan migratebuilds it.- A Model (Student) speaks for its table (students) — that naming convention is the whole wiring.
- Eloquent:
all(),find(),findOrFail(), and new-model +save()for writing. - Rollback exists: migrations can be undone as cleanly as they were built.
Common mistakes
- Forgetting to create the empty database — migrate fails with "Unknown database". Fix: create
school_appin MySQL/phpMyAdmin first; Laravel builds tables, not the database itself. - Editing a migration that already ran — migrate skips files it has recorded, so your edit never applies and confusion follows. Fix: make a NEW migration for every change (
add_city_to_students_table), ormigrate:freshon a practice app to rebuild everything (it deletes all data — practice apps only). - Model/table name mismatch — naming the model
Studentsor the tablestudentbreaks the silent convention. Fix: model singular (Student), table plural (students) — or declareprotected $tableopenly when you must differ. - Raw SQL everywhere out of habit — hand-written queries in controllers work, but throw away Eloquent's safety and readability. Fix: reach for the model first; drop to raw SQL only when a query truly demands it.
Practice
- Add a
feestable by migration (student_id,amount,paid_atnullable) and aFeemodel. Run migrate, then roll back once and migrate again — write down whatdown()did. - Save all five series students through the model, then print a merit list with
Student::all()sorted by hand in PHP (highest marks first). Confirm the order: Hira 95, Sara 92, Ali 88, Bilal 76, Usman 64.