Migrations and validation
Versioned schema changes you can roll back, and rules that keep unvalidated input away from the database.
Migrations
php artisan make:migration create_posts_table --create=posts
php artisan migrate
php artisan migrate:rollback --step=1
php artisan migrate:status<?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->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title', 200);
$table->text('body');
$table->timestamp('published_at')->nullable();
$table->timestamps();
$table->index(['published_at', 'user_id']);
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};- One migration per schema change, and never edit a migration that has already run in production — add a new one instead.
foreignId(...)->constrained()creates the column, the index and the foreign key in a single call.migrate:rollbackrunsdown()in reverse order, so keep it correct and a bad deploy stays reversible.migrate:freshdrops every table and reapplies everything; it is for local development only, never for a shared environment.- Seeders belong in
database/seedersand should be idempotent so they can run repeatedly.
Validation and form requests
php artisan make:request StorePostRequest<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('create', Post::class);
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:200'],
'body' => ['required', 'string', 'min:20'],
'slug' => ['required', Rule::unique('posts', 'slug')->ignore($this->post)],
'tags' => ['array'],
'tags.*' => ['exists:tags,id'],
];
}
}public function store(StorePostRequest $request)
{
// runs only if authorize() passed and the rules passed
return Post::create($request->validated());
}| Rule | What it checks |
|---|---|
required | The key is present and not empty |
sometimes | Validate only when the key is present |
unique:table,column | No duplicate, with ignore() for updates |
exists:table,column | The value references an existing row |
array and field.* | Validate each element of a list |
Rule::in([...]) | Restrict the value to an allowlist |
⚠️
$request->all() passes every submitted field into create(), including fields the form never showed. Use $request->validated() so only rule-approved data reaches the database, and keep $fillable as a second line of defence.FAQ
Where do validation failures go?
For a web request, back to the form with the errors flashed to the session and available as
$errors. For a JSON request, a 422 response with an errors object. Neither needs code in the action.Form request or inline validate()?
Inline
$request->validate() for a small endpoint with two or three rules. Move to a FormRequest once the rules grow, need an authorisation check, or are reused by several actions.Related
Controllers, Blade views and Eloquent models Installation and routing
Last refreshed 2026-09-18.