Crie uma base confiável de estados e municípios brasileiros no Laravel usando dados oficiais do IBGE.
Em muitos projetos Laravel, principalmente sistemas administrativos, ERPs e SaaS, surge a necessidade de trabalhar com Estados e Cidades do Brasil.
O problema é que:
- cadastrar isso manualmente é inviável
- seeds estáticos ficam desatualizados
- copiar dumps da internet não é confiável
Neste tutorial, vou mostrar como criar uma estrutura profissional de Estados e Cidades no Laravel, consumindo diretamente a API oficial e gratuita do IBGE, garantindo dados corretos e atualizados.
Você vai aprender a:
- criar migrações bem estruturadas
- definir relacionamentos Eloquent
- criar um comando Artisan para importar os dados
- popular o banco com segurança e sem duplicações
📦 O que vamos construir
- Tabela states
- Tabela cities
- Models com relacionamento
Comando Artisan:
php artisan ibge:import-locations
1️⃣ Criando os models e migrations
Vamos começar criando os models já com suas migrations:
php artisan make:model State -m
php artisan make:model City -m
📄 Migration da tabela states
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('states', function (Blueprint $table) {
$table->id();
$table->string('uf', 2)->unique(); // SP, RJ...
$table->string('name')->index(); // São Paulo
$table->unsignedInteger('ibge_id')->unique(); // ID oficial do IBGE
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('states');
}
};
💡 Por que essas colunas?
uf: facilita exibição e filtrosibge_id: garante integridade com dados oficiaisunique: evita duplicaçõesindex: melhora buscas por nome
📄 Migration da tabela cities
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('cities', function (Blueprint $table) {
$table->id();
$table->foreignId('state_id')
->constrained('states')
->cascadeOnDelete();
$table->string('name')->index();
$table->unsignedInteger('ibge_id')->unique();
$table->timestamps();
// evita cidades duplicadas dentro do mesmo estado
$table->unique(['state_id', 'name']);
});
}
public function down(): void
{
Schema::dropIfExists('cities');
}
};
▶️ Executando as migrations
php artisan migrate
2️⃣ Criando os models e relacionamentos
🧠 Model State
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class State extends Model
{
protected $fillable = ['uf', 'name', 'ibge_id'];
public function cities(): HasMany
{
return $this->hasMany(City::class);
}
}
🧠 Model City
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class City extends Model
{
protected $fillable = ['state_id', 'name', 'ibge_id'];
public function state(): BelongsTo
{
return $this->belongsTo(State::class);
}
}
Agora você já pode fazer:
$state->cities;
$city->state;
3️⃣ Criando o comando Artisan para importar do IBGE
Em vez de usar seed fixo, vamos criar um comando reutilizável, que pode ser executado sempre que necessário.
php artisan make:command IbgeImportLocations
📄 Comando IbgeImportLocations
namespace App\Console\Commands;
use App\Models\City;
use App\Models\State;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
class IbgeImportLocations extends Command
{
protected $signature = 'ibge:import-locations';
protected $description = 'Importa estados e cidades do IBGE para o banco local';
public function handle(): int
{
$this->info('Buscando estados no IBGE...');
$statesResponse = Http::timeout(60)
->get('https://servicodados.ibge.gov.br/api/v1/localidades/estados');
if (! $statesResponse->successful()) {
$this->error('Falha ao consultar estados no IBGE.');
return self::FAILURE;
}
$states = collect($statesResponse->json())
->sortBy('nome')
->values();
foreach ($states as $s) {
$state = State::updateOrCreate(
['ibge_id' => $s['id']],
['uf' => $s['sigla'], 'name' => $s['nome']]
);
$this->info("Importando cidades de {$state->uf}...");
$citiesResponse = Http::timeout(120)
->get("https://servicodados.ibge.gov.br/api/v1/localidades/estados/{$state->ibge_id}/municipios");
if (! $citiesResponse->successful()) {
$this->warn("Falha ao buscar cidades de {$state->uf}. Pulando...");
continue;
}
foreach ($citiesResponse->json() as $c) {
City::updateOrCreate(
['ibge_id' => $c['id']],
[
'state_id' => $state->id,
'name' => $c['nome'],
]
);
}
}
$this->info('Importação concluída com sucesso.');
return self::SUCCESS;
}
}
4️⃣ Executando a importação
php artisan ibge:import-locations
Ao final você terá:
- 27 estados
- mais de 5.500 cidades
- dados oficiais
- sem duplicações
✅ Vantagens dessa abordagem
- 📌 Dados oficiais do IBGE
- 📌 Comando reutilizável
- 📌 Evita seeds desatualizados
- 📌 Fácil manutenção
- 📌 Pronto para produção
🧠 Conclusão
Essa é uma forma simples, robusta e profissional de manter Estados e Cidades no Laravel.
Você pode usar essa estrutura em:
- formulários de endereço
- filtros geográficos
- cadastros de usuários
- sistemas administrativos
- SaaS multi-região