Migrations define your database schema in PHP code. Eloquent ORM lets you interact with your database using expressive, object-oriented syntax.
Configure your database in .env. Laravel reads this into config/database.php.
# MySQL (most common) DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=blog_db DB_USERNAME=root DB_PASSWORD= # SQLite (zero-config for prototyping) DB_CONNECTION=sqlite # DB_DATABASE=/absolute/path/to/database.sqlite # Or just create: touch database/database.sqlite # PostgreSQL DB_CONNECTION=pgsql DB_HOST=127.0.0.1 DB_PORT=5432 DB_DATABASE=blog_db DB_USERNAME=postgres DB_PASSWORD=secret
DB_PASSWORD empty — XAMPP root has no password by default.# Create migration for a new table php artisan make:migration create_posts_table --create=posts # Create migration to modify existing table php artisan make:migration add_excerpt_to_posts_table --table=posts # Create model AND migration together (recommended) php artisan make:model Post -m # Creates: app/Models/Post.php # database/migrations/xxxx_create_posts_table.php
Generated migration file (database/migrations/2024_01_01_000000_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(); // BIGINT UNSIGNED AUTO_INCREMENT PK
$table->foreignId('user_id')
->constrained()
->cascadeOnDelete(); // FK to users.id
$table->string('title'); // VARCHAR(255)
$table->string('slug')->unique(); // unique slug
$table->text('body'); // TEXT
$table->string('image')->nullable(); // optional image path
$table->boolean('published')->default(false);
$table->timestamp('published_at')->nullable();
$table->timestamps(); // created_at + updated_at
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
| Method | SQL Type | Notes |
|---|---|---|
$table->id() | BIGINT UNSIGNED PK | Auto-increment primary key |
$table->string('col') | VARCHAR(255) | Add length: string('col', 100) |
$table->text('col') | TEXT | For longer text |
$table->longText('col') | LONGTEXT | Very long content |
$table->integer('col') | INT | Also: tinyInteger, bigInteger |
$table->float('col') | FLOAT | Decimal: decimal('price', 8, 2) |
$table->boolean('col') | TINYINT(1) | true/false |
$table->date('col') | DATE | Date only |
$table->timestamp('col') | TIMESTAMP | Date and time |
$table->timestamps() | 2x TIMESTAMP | created_at + updated_at |
$table->softDeletes() | TIMESTAMP NULL | deleted_at for soft delete |
$table->foreignId('user_id') | BIGINT UNSIGNED | Foreign key column |
->constrained() | FK constraint | Chain after foreignId |
->nullable() | NULL allowed | Chain on any column |
->default(value) | DEFAULT | Chain on any column |
->unique() | UNIQUE index | Chain on any column |
->index() | INDEX | For faster lookups |
$table->json('col') | JSON | Store JSON data |
$table->enum('status', [...]) | ENUM | Fixed list of values |
| Command | What It Does |
|---|---|
php artisan migrate | Run all pending migrations |
php artisan migrate:status | Show which migrations have run |
php artisan migrate:rollback | Undo last batch of migrations |
php artisan migrate:rollback --step=3 | Undo last 3 batches |
php artisan migrate:reset | Undo ALL migrations |
php artisan migrate:fresh | Drop all tables + re-migrate |
php artisan migrate:fresh --seed | Fresh + run seeders |
php artisan migrate:refresh | Rollback all + re-migrate |
migrate:fresh drops ALL tables — never run it on production!<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Post extends Model
{
use HasFactory, SoftDeletes;
// Columns allowed for mass assignment
protected $fillable = ['title', 'slug', 'body', 'image', 'published', 'user_id'];
// Columns hidden from JSON/array output
protected $hidden = ['internal_notes'];
// Automatic type casting
protected $casts = [
'published' => 'boolean',
'published_at' => 'datetime',
'meta' => 'array', // JSON column auto-decoded
];
// Custom table name (default = plural snake_case of class)
// protected $table = 'blog_posts';
// Accessor: $post->title returns title-cased value
public function getTitleAttribute($value)
{
return ucwords($value);
}
// Mutator: auto-generate slug when title is set
public function setTitleAttribute($value)
{
$this->attributes['title'] = $value;
$this->attributes['slug'] = Str::slug($value);
}
}
// Mass assignment (requires $fillable)
$post = Post::create([
'title' => 'My First Post',
'body' => 'Content here...',
'user_id' => 1,
]);
// Set and save individually
$post = new Post();
$post->title = 'Another Post';
$post->body = 'Content...';
$post->save();
$posts = Post::all(); // All records
$post = Post::find(1); // Find by PK (null if not found)
$post = Post::findOrFail(1); // Find or throw 404
$posts = Post::where('published', true)->get(); // WHERE clause
$post = Post::where('slug', 'my-post')->first(); // First match
$count = Post::where('published', true)->count();
$posts = Post::latest()->paginate(10); // Paginated results
// Update via instance
$post = Post::findOrFail(1);
$post->update(['title' => 'Updated Title']);
// Mass update
Post::where('user_id', 5)->update(['published' => false]);
// Delete via instance
$post = Post::findOrFail(1);
$post->delete();
// Delete by condition
Post::where('published', false)->delete();
// Soft delete (if model uses SoftDeletes trait)
$post->delete(); // sets deleted_at
Post::withTrashed()->get(); // include soft-deleted
Post::onlyTrashed()->get(); // only soft-deleted
$post->restore(); // un-delete
$post->forceDelete(); // permanent delete
// User hasMany Posts
class User extends Model {
public function posts() {
return $this->hasMany(Post::class);
}
}
// Post belongsTo User
class Post extends Model {
public function user() {
return $this->belongsTo(User::class);
}
}
// Usage
$posts = $user->posts; // all posts by user
$user = $post->user; // post's author
$posts = $user->posts()->where('published', true)->get();
class User extends Model {
public function profile() {
return $this->hasOne(Profile::class);
}
}
$profile = $user->profile;
$user = $profile->user; // via belongsTo(User::class)
class Post extends Model {
public function tags() {
return $this->belongsToMany(Tag::class);
// Needs pivot table: post_tag (post_id, tag_id)
}
}
// Attach/detach tags
$post->tags()->attach([1, 3, 5]);
$post->tags()->detach(3);
$post->tags()->sync([1, 5]); // replace all
$tagNames = $post->tags->pluck('name');
// BAD: N+1 queries (1 for posts + 1 per post for user)
$posts = Post::all();
foreach ($posts as $post) { echo $post->user->name; }
// GOOD: 2 queries total
$posts = Post::with('user')->get();
$posts = Post::with(['user', 'tags'])->get(); // multiple
use Illuminate\Support\Facades\DB;
// Basic select
$posts = DB::table('posts')->get();
$post = DB::table('posts')->where('id', 1)->first();
// Chained methods
$posts = DB::table('posts')
->select('title', 'body', 'created_at')
->where('published', true)
->where('user_id', 1)
->orderBy('created_at', 'desc')
->limit(10)
->get();
// Joins
$posts = DB::table('posts')
->join('users', 'posts.user_id', '=', 'users.id')
->select('posts.title', 'users.name as author')
->get();
// Aggregates
$count = DB::table('posts')->where('published', true)->count();
$max = DB::table('posts')->max('views');
$avg = DB::table('posts')->avg('rating');
php artisan make:seeder PostSeeder
// database/seeders/PostSeeder.php
class PostSeeder extends Seeder
{
public function run(): void
{
Post::create([
'title' => 'Hello World',
'body' => 'First post content',
'user_id' => 1,
'published' => true,
]);
}
}
// Run it
php artisan db:seed --class=PostSeeder
php artisan make:factory PostFactory --model=Post
// database/factories/PostFactory.php
class PostFactory extends Factory
{
public function definition(): array
{
return [
'title' => fake()->sentence(),
'body' => fake()->paragraphs(3, true),
'slug' => fake()->slug(),
'published' => fake()->boolean(),
'user_id' => User::factory(), // creates a user too
];
}
}
// DatabaseSeeder.php
Post::factory(50)->create(); // create 50 fake posts
// Run all seeders
php artisan db:seed