O que é Laravel?
Laravel é um framework PHP full-stack construído em torno de uma promessa simples: tornar o desenvolvimento web prazeroso sem sacrificar o poder. Ele segue o padrão MVC, mas a arquitetura importa menos do que a experiência de usá-lo. O roteamento é expressivo, a camada de banco de dados parece inglês e as peças que você normalmente teria que montar usando diversas bibliotecas já estão integradas.
Foi criado por Taylor Otwell em 2011 e, desde então, tornou-se o framework PHP mais popular, com um ecossistema que vai muito além do framework em si: Forge e Vapor para deployment, Nova para painéis administrativos, Livewire para interfaces reativas e Sanctum para autenticação.
Se você já escreveu PHP “na mão”, o Laravel passa a sensação de que alguém finalmente organizou a caixa de ferramentas.
Roteamento e controllers
Toda requisição entra através de uma rota. As rotas web ficam em routes/web.php e as rotas de API em routes/api.php, e ambas utilizam a mesma sintaxe expressiva.
<?php
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
Route::get('/posts', [PostController::class, 'index']);
Route::get('/posts/{post}', [PostController::class, 'show']);
Route::post('/posts', [PostController::class, 'store']);
Route::apiResource('posts', PostController::class);
O Route::apiResource gera o conjunto convencional de rotas index, store, show, update e destroy em apenas uma linha. Um controller então contém um método por ação, mantendo cada handler pequeno.
class PostController extends Controller
{
public function index()
{
return Post::latest()->paginate(20);
}
public function store(StorePostRequest $request)
{
$post = $request->user()->posts()->create($request->validated());
return response()->json($post, 201);
}
}
Route model binding
O Laravel consegue transformar automaticamente um parâmetro de rota em uma instância de model. Nomeie o parâmetro de acordo com o model e utilize o type-hint no controller; assim, o Laravel busca o registro ou retorna um 404.
Route::get('/posts/{post}', [PostController::class, 'show']);
public function show(Post $post)
{
return $post->load('author');
}
Por padrão, a chave primária do model é utilizada. Adicione getRouteKeyName() para realizar o bind através de um slug, permitindo que você tenha URLs limpas como /posts/hello-world sem a necessidade de código extra de busca.
Eloquent: o ORM que conquista os desenvolvedores
O Eloquent é o ORM active-record do Laravel. Um model mapeia para uma tabela, e os relacionamentos são declarados como métodos.
class Post extends Model
{
protected $fillable = ['title', 'slug', 'body'];
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function scopePublished($query)
{
return $query->whereNotNull('published_at');
}
}
Os relacionamentos incluem hasMany, belongsTo, belongsToMany, hasManyThrough e variantes polimórficas. A regra prática mais importante é fazer o eager load dos relacionamentos que você sabe que irá utilizar:
$posts = Post::with('author')->published()->latest()->get();
Sem o with('author'), acessar $post->author dentro de um loop dispara uma query por linha — o clássico problema N+1. O eager loading transforma isso em apenas duas queries, independentemente de quantos posts você busque.
Migrations, factories e seeders
Mudanças de schema são arquivos PHP versionados, garantindo que cada ambiente seja construído a partir do mesmo histórico.
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->timestamps();
});
Execute-os com php artisan migrate. Factories descrevem como simular um model, e seeders populam o banco de dados com dados conhecidos.
Post::factory()->count(50)->for(User::factory())->create();
Juntos, factories e seeders tornam os testes realistas sem a necessidade de fixtures escritas manualmente.
Blade: templating sem surpresas
O Blade é compilado para PHP puro e não atrapalha o seu fluxo de trabalho. A saída é escapada por padrão, e os layouts e componentes mantêm a marcação DRY.
{{-- resources/views/posts/show.blade.php --}}
@extends('layouts.app')
@section('content')
<article>
<h1>{{ $post->title }}</h1>
<p>By {{ $post->author->name }}</p>
{!! $post->body !!}
</article>
@endsection
{{ }} escapa caracteres, {!! !!} não. Use a forma escapada em todos os lugares, a menos que você mesmo tenha sanitizado o conteúdo, e prefira componentes como <x-post-card :post="$post" /> para UI reutilizável.
Validação e form requests
A validação é um recurso nativo e prioritário. Para qualquer coisa que não seja pontual, mova as regras para um form request.
php artisan make:request StorePostRequest
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user() !== null;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string'],
'published_at' => ['nullable', 'date'],
];
}
}
Use o type-hint do request no controller e o Laravel fará a validação antes mesmo do seu código ser executado, retornando automaticamente um 422 com os erros. Em seguida, utilize $request->validated() para garantir que apenas os campos conhecidos cheguem ao model.
Middleware e o ciclo de vida da requisição
O middleware filtra as requisições antes que elas cheguem a uma rota. Versões recentes do Laravel os configuram em bootstrap/app.php.
->withMiddleware(function (Middleware $middleware) {
$middleware->api(prepend: [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
]);
$middleware->alias([
'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
]);
})
Uma requisição passa pelos middleware globais, depois pelos middleware de rota, como auth, e então chega ao controller. Entender essa ordem explica a maioria das dúvidas do tipo “por que este valor está faltando”.
O service container e facades
O service container resolve as dependências para você. Basta utilizar type-hint de uma classe em um construtor ou controller e o Laravel a instanciará, injetando suas próprias dependências recursivamente.
class PostController extends Controller
{
public function __construct(private PostRepository $posts) {}
}
Facades como Cache::get() e DB::table() oferecem um atalho com aparência estática para serviços no container. Elas são convenientes em controllers, mas a injeção via construtor mantém as classes testáveis e as dependências explícitas. Prefira a injeção em services; use facades onde elas genuinamente tornem a leitura melhor.
Artisan, filas e eventos
O Artisan é a interface de linha de comando que gera e executa a aplicação.
php artisan make:model Post -mfc # model, migration, factory, controller
php artisan migrate
php artisan queue:work
php artisan schedule:run
Qualquer tarefa lenta — enviar e-mails, chamar uma API de terceiros, gerar um relatório — deve ser colocada em um job de fila.
class SendPostNotification implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;
public function __construct(public Post $post) {}
public function handle(): void
{
// runs on a worker, not in the request
}
}
SendPostNotification::dispatch($post);
Eventos e listeners seguem o mesmo padrão, e os listeners podem implementar ShouldQueue por conta própria, fazendo com que a requisição retorne imediatamente.
Autenticação com Sanctum
Para SPAs e APIs baseadas em tokens, o Sanctum é a escolha padrão. Ele emite tokens de acesso pessoal e também suporta sessões baseadas em cookies para frontends de primeira parte.
$token = $user->createToken('mobile')->plainTextToken;
Proteja as rotas com o alias do middleware:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
O Passport continua sendo a opção quando você precisa de um servidor OAuth2 completo com clientes, scopes e grants.
Testando com Pest e PHPUnit
O Laravel já vem com uma configuração de testes que inicializa o framework e pode resetar o banco de dados a cada teste utilizando a trait RefreshDatabase.
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('it creates a post', function () {
$user = User::factory()->create();
$this->actingAs($user)
->postJson('/api/posts', ['title' => 'Hello', 'body' => 'World'])
->assertCreated();
});
O Pest adiciona uma sintaxe baseada em funções, mais legível, sobre o PHPUnit. De qualquer forma, os testes HTTP exercitam o roteamento, middleware, validação e o banco de dados em conjunto, que é onde a maioria dos bugs do Laravel realmente acontece.
Melhores práticas
- Mantenha os controllers enxutos; mova a lógica de negócio para services e actions.
- Use form requests para validação e persista apenas
$request->validated(). - Utilize eager load em relacionamentos com
with()para evitar queries N+1. - Versione o schema com migrations e gere dados com factories.
- Prefira a injeção via construtor em vez de facades nos services que você deseja testar.
- Coloque em fila (queue) qualquer processo lento para manter as requisições rápidas.
- Escreva feature tests que exercitem a camada HTTP e um banco de dados real.
Erros comuns
- Chamar
Post::create($request->all())e expor colunas ao mass assignment. - Fazer lazy loading de relacionamentos dentro de um loop e sobrecarregar o banco de dados.
- Colocar queries, validações e regras de negócio em um único método gigante no controller.
- Esquecer o
$fillablee se perguntar por que os atributos estão sendo descartados silenciosamente. - Executar tarefas pesadas de forma síncrona em vez de disparar um job.
- Editar uma migration antiga depois que ela já foi executada em produção.
- Assumir que
{{ }}e{!! !!}são intercambiáveis.
Próximos passos
O Laravel ensina uma maneira produtiva e opinativa de construir aplicações web, e grande parte de sua base vem de componentes do Symfony, por isso aquele guia é um complemento natural. Se você vem do mundo JavaScript, compare a mentalidade com o Express. Para projetar a API que suas rotas expõem, leia sobre REST e, em seguida, OpenAPI para documentação.