A fluent ViaCEP API wrapper for Laravel
eliel-elie/laravel-fluent-viacep is a Laravel package for a fluent viacep api wrapper for laravel.
It currently has 0 GitHub stars and 2 downloads on Packagist (latest version v1.0.1).
Install it with composer require eliel-elie/laravel-fluent-viacep.
Discover more Laravel packages by eliel-elie
or browse all Laravel packages to compare alternatives.
Last updated
A modern, fluent, and highly customizable wrapper for the ViaCEP API, designed specifically for Laravel applications. Built with PHP 8.2+ features and following best practices for production-ready Laravel packages.
ViaCEP is a free Brazilian postal code (CEP) lookup service that provides address information based on postal codes or vice-versa.
Address object using PHP 8.2+ readonly propertiesYou can install the package via composer:
composer require eliel-elie/laravel-fluent-viacep
The service provider will automatically register itself.
You can publish the config file with:
php artisan vendor:publish --tag="viacep-config"
You can publish the config file with:
php artisan vendor:publish --tag="viacep-config"
This is the contents of the published config file (config/viacep.php):
return [
// Base URL for ViaCEP API
'base_url' => env('VIACEP_BASE_URL', 'https://viacep.com.br/ws'),
// Request timeout in seconds
'timeout' => env('VIACEP_TIMEOUT', 10),
// Number of retry attempts on failure
'retry' => env('VIACEP_RETRY', 3),
// Enable/disable caching
'cache_enabled' => env('VIACEP_CACHE_ENABLED', true),
// Cache TTL in seconds (1 hour default)
'cache_ttl' => env('VIACEP_CACHE_TTL', 3600),
// Cache key prefix
'cache_prefix' => env('VIACEP_CACHE_PREFIX', 'viacep'),
// Default response format (json, xml, piped, jsonp)
'default_format' => env('VIACEP_DEFAULT_FORMAT', 'json'),
];
Add these to your .env file to customize the package behavior:
VIACEP_BASE_URL=https://viacep.com.br/ws
VIACEP_TIMEOUT=10
VIACEP_RETRY=3
VIACEP_CACHE_ENABLED=true
VIACEP_CACHE_TTL=3600
VIACEP_CACHE_PREFIX=viacep
VIACEP_DEFAULT_FORMAT=json
use ViaCep\Facades\ViaCep;
// Simple CEP lookup
$address = ViaCep::cep('01001000')->get();
echo $address->logradouro; // Praça da Sé
echo $address->localidade; // São Paulo
echo $address->uf; // SP
// Basic search
$address = ViaCep::cep('01001000')->get();
// With formatting
$address = ViaCep::cep('01001-000')->get(); // Automatically cleans formatting
// CEP validation is automatic
try {
$address = ViaCep::cep('invalid')->get();
} catch (\ViaCep\Exceptions\InvalidCepException $e) {
// Handle invalid CEP
}
Search for multiple addresses matching your criteria:
// Returns array of Address objects
$addresses = ViaCep::state('SP')
->city('São Paulo')
->street('Praça da Sé')
->get();
foreach ($addresses as $address) {
echo $address->cep . PHP_EOL;
}
Requirements for address search:
state(): Required - 2-letter state code (UF)city(): Required - City namestreet(): Required - Minimum 3 charactersFetch multiple CEPs efficiently in a single call:
$ceps = ['01001000', '20040-020', '30130-100'];
$results = ViaCep::bulk($ceps)->get();
// Results are indexed by CEP
foreach ($results as $cep => $address) {
echo "{$cep}: {$address->localidade}" . PHP_EOL;
}
Features:
The package supports all ViaCEP API response formats:
$address = ViaCep::cep('01001000')->asJson()->get();
// Returns: Address DTO object
$address = ViaCep::cep('01001000')->asXml()->get();
// Returns: Address DTO object parsed from XML
$address = ViaCep::cep('01001000')->asPiped()->get();
// Returns: Address DTO object parsed from piped string
$response = ViaCep::cep('01001000')
->asJsonp('myCallback')
->raw(); // Returns JSONP string
// Or using alias
$response = ViaCep::cep('01001000')
->jsonp('handleAddress')
->raw();
Get the raw API response without DTO parsing:
// Get raw JSON string
$jsonString = ViaCep::cep('01001000')->asJson()->raw();
// Get raw XML string
$xmlString = ViaCep::cep('01001000')->asXml()->raw();
// Get raw piped string
$pipedString = ViaCep::cep('01001000')->asPiped()->raw();
// Cache for 1 hour (3600 seconds)
$address = ViaCep::cep('01001000')
->cache(3600)
->get();
// Cache for 24 hours
$address = ViaCep::cep('01001000')
->cache(86400)
->get();
// With custom cache key
$address = ViaCep::cep('01001000')
->cache(3600, 'my-custom-key')
->get();
// Disable cache for this request only
$address = ViaCep::cep('01001000')
->withoutCache()
->get();
Cache Key Structure:
{cache_prefix}:md5(url){cache_prefix}:{custom_key}viacep (configurable)Control resilience and performance:
$address = ViaCep::cep('01001000')
->timeout(5) // Set timeout to 5 seconds
->retry(3) // Retry up to 3 times on failure
->get();
// Combine with caching for maximum resilience
$address = ViaCep::cep('01001000')
->timeout(10)
->retry(5)
->cache(7200)
->get();
Default values:
Apply custom transformations to the response:
$customData = ViaCep::cep('01001000')
->transform(function ($response) {
$data = json_decode($response, true);
return [
'postal_code' => $data['cep'],
'full_address' => "{$data['logradouro']}, {$data['localidade']}",
'custom_field' => 'my custom value'
];
})
->get();
// Use with any format
$transformed = ViaCep::cep('01001000')
->asXml()
->transform(function ($xmlString) {
$xml = simplexml_load_string($xmlString);
return (array) $xml;
})
->get();
Built-in static methods for CEP manipulation:
// Validate CEP format
ViaCep::validate('01001-000'); // true
ViaCep::validate('01001000'); // true
ViaCep::validate('12345'); // false
// Format CEP (add hyphen)
ViaCep::formatCep('01001000'); // 01001-000
ViaCep::formatCep('12345'); // 12345 (invalid, returns as-is)
// Clean CEP (remove formatting)
ViaCep::clean('01001-000'); // 01001000
ViaCep::clean('01.001-000'); // 01001000
Override the API base URL (useful for testing):
$address = ViaCep::cep('01001000')
->setBaseUrl('https://custom-api.example.com/ws')
->get();
All configuration methods return $this for fluent chaining:
$address = ViaCep::cep('01001000')
->asJson()
->cache(3600, 'custom-key')
->timeout(15)
->retry(5)
->get();
// Address search with full configuration
$addresses = ViaCep::state('SP')
->city('São Paulo')
->street('Avenida Paulista')
->timeout(20)
->cache(7200)
->retry(3)
->get();
The Address Data Transfer Object provides a type-safe, immutable representation of address data.
All properties are readonly and publicly accessible:
$address->cep; // string: CEP (postal code)
$address->logradouro; // string: Street name
$address->complemento; // string: Complement
$address->bairro; // string: Neighborhood
$address->localidade; // string: City name
$address->uf; // string: State (UF)
$address->ibge; // string: IBGE code
$address->gia; // string: GIA code
$address->ddd; // string: Area code
$address->siafi; // string: SIAFI code
getFullAddress(): stringReturns a complete formatted address:
$address = ViaCep::cep('01001000')->get();
echo $address->getFullAddress();
// Output: "Praça da Sé, lado ímpar, Sé, São Paulo, SP, 01001-000"
getFormattedCep(): stringReturns CEP with formatting (hyphen):
echo $address->getFormattedCep(); // 01001-000
getCity(): stringAlias for localidade:
echo $address->getCity(); // Same as $address->localidade
getState(): stringAlias for uf:
echo $address->getState(); // Same as $address->uf
getStreet(): stringAlias for logradouro:
echo $address->getStreet(); // Same as $address->logradouro
getNeighborhood(): stringAlias for bairro:
echo $address->getNeighborhood(); // Same as $address->bairro
getIbgeCode(): stringReturns the IBGE municipal code:
echo $address->getIbgeCode(); // Same as $address->ibge
isComplete(): boolChecks if the address has all main fields populated:
if ($address->isComplete()) {
echo "Address is complete";
}
// Checks: logradouro, bairro, localidade, and uf
toArray(): arrayConverts the Address to an associative array:
$data = $address->toArray();
/*
[
'cep' => '01001-000',
'logradouro' => 'Praça da Sé',
'complemento' => 'lado ímpar',
'bairro' => 'Sé',
'localidade' => 'São Paulo',
'uf' => 'SP',
'ibge' => '3550308',
'gia' => '1004',
'ddd' => '11',
'siafi' => '7107'
]
*/
toJson(int $options = 0): stringConverts the Address to JSON:
$json = $address->toJson();
$prettyJson = $address->toJson(JSON_PRETTY_PRINT);
__toString(): stringString representation returns full address:
echo $address; // Calls getFullAddress()
$address = \ViaCep\DTO\Address::fromArray([
'cep' => '01001-000',
'logradouro' => 'Praça da Sé',
'complemento' => 'lado ímpar',
'bairro' => 'Sé',
'localidade' => 'São Paulo',
'uf' => 'SP',
'ibge' => '3550308',
'gia' => '1004',
'ddd' => '11',
'siafi' => '7107'
]);
The Address DTO implements JsonSerializable:
$address = ViaCep::cep('01001000')->get();
// Direct JSON encoding
$json = json_encode($address);
// In Laravel responses
return response()->json($address);
// In Laravel collections
$addresses = collect([$address1, $address2]);
return $addresses->toJson();
The package throws specific exceptions for different error scenarios:
InvalidCepExceptionThrown when CEP format is invalid:
use ViaCep\Exceptions\InvalidCepException;
try {
$address = ViaCep::cep('invalid')->get();
} catch (InvalidCepException $e) {
// Handle invalid CEP format
echo "Invalid CEP: " . $e->getMessage();
}
CepNotFoundExceptionThrown when CEP is not found in the database:
use ViaCep\Exceptions\CepNotFoundException;
try {
$address = ViaCep::cep('99999999')->get();
} catch (CepNotFoundException $e) {
// Handle CEP not found
echo "CEP not found";
}
UnsupportedFormatExceptionThrown when an unsupported response format is requested:
use ViaCep\Exceptions\UnsupportedFormatException;
try {
$address = ViaCep::cep('01001000')
->format('invalid-format')
->get();
} catch (UnsupportedFormatException $e) {
// Handle unsupported format
}
ViaCepExceptionBase exception class for all package exceptions:
use ViaCep\Exceptions\ViaCepException;
try {
$address = ViaCep::cep('01001000')->get();
} catch (ViaCepException $e) {
// Catches all package-specific exceptions
Log::error('ViaCEP Error: ' . $e->getMessage());
}
Exception
└── ViaCepException (base)
├── InvalidCepException
├── CepNotFoundException
└── UnsupportedFormatException
use ViaCep\Facades\ViaCep;
use ViaCep\Exceptions\InvalidCepException;
use ViaCep\Exceptions\CepNotFoundException;
use Illuminate\Support\Facades\Log;
try {
$address = ViaCep::cep($userInput)
->timeout(5)
->retry(2)
->get();
// Process address
return $address;
} catch (InvalidCepException $e) {
// Handle validation error
return response()->json([
'error' => 'Invalid CEP format'
], 422);
} catch (CepNotFoundException $e) {
// Handle not found
return response()->json([
'error' => 'CEP not found'
], 404);
} catch (\RuntimeException $e) {
// Handle network/API errors
Log::error('ViaCEP API Error', ['error' => $e->getMessage()]);
return response()->json([
'error' => 'Service temporarily unavailable'
], 503);
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use ViaCep\Facades\ViaCep;
use ViaCep\Exceptions\InvalidCepException;
use ViaCep\Exceptions\CepNotFoundException;
class AddressController extends Controller
{
public function show(Request $request): JsonResponse
{
$request->validate([
'cep' => 'required|string|size:8'
]);
try {
$address = ViaCep::cep($request->cep)
->cache(3600)
->retry(3)
->get();
return response()->json([
'success' => true,
'data' => $address
]);
} catch (InvalidCepException $e) {
return response()->json([
'success' => false,
'error' => 'Invalid CEP format'
], 422);
} catch (CepNotFoundException $e) {
return response()->json([
'success' => false,
'error' => 'CEP not found'
], 404);
}
}
public function search(Request $request): JsonResponse
{
$request->validate([
'state' => 'required|string|size:2',
'city' => 'required|string|min:3',
'street' => 'required|string|min:3'
]);
$addresses = ViaCep::state($request->state)
->city($request->city)
->street($request->street)
->cache(7200)
->get();
return response()->json([
'success' => true,
'data' => $addresses,
'count' => count($addresses)
]);
}
}
<?php
namespace App\Livewire;
use Livewire\Component;
use ViaCep\Facades\ViaCep;
class AddressForm extends Component
{
public $cep = '';
public $address = null;
public $error = null;
public function searchCep()
{
$this->error = null;
$this->address = null;
if (!ViaCep::validate($this->cep)) {
$this->error = 'Invalid CEP format';
return;
}
try {
$this->address = ViaCep::cep($this->cep)
->cache(3600)
->get();
} catch (\Exception $e) {
$this->error = 'CEP not found';
}
}
public function render()
{
return view('livewire.address-form');
}
}
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use ViaCep\Facades\ViaCep;
class ImportAddresses extends Command
{
protected $signature = 'addresses:import {file}';
protected $description = 'Import addresses from CEP list';
public function handle()
{
$ceps = file($this->argument('file'), FILE_IGNORE_NEW_LINES);
$this->info("Importing " . count($ceps) . " addresses...");
$results = ViaCep::bulk($ceps)
->cache(86400)
->get();
$this->info("Successfully imported " . count($results) . " addresses");
foreach ($results as $cep => $address) {
$this->line("{$cep}: {$address->localidade} - {$address->uf}");
}
return Command::SUCCESS;
}
}
<?php
namespace App\Services;
use ViaCep\Facades\ViaCep;
use ViaCep\DTO\Address;
class AddressService
{
public function findByCep(string $cep): ?Address
{
try {
return ViaCep::cep($cep)
->cache(3600)
->retry(3)
->get();
} catch (\Exception $e) {
report($e);
return null;
}
}
public function searchAddress(string $state, string $city, string $street): array
{
return ViaCep::state($state)
->city($city)
->street($street)
->cache(7200)
->get();
}
public function validateAndFormat(string $cep): ?string
{
if (!ViaCep::validate($cep)) {
return null;
}
return ViaCep::formatCep($cep);
}
}
The package includes comprehensive tests using Pest PHP.
# Run all tests
composer test
# Run with coverage
vendor/bin/pest --coverage
# Run specific test file
vendor/bin/pest tests/Feature/ViaCepClientTest.php
tests/
├── Feature/
│ └── ViaCepClientTest.php # Integration tests
├── Unit/
│ ├── AddressTest.php # Address DTO tests
│ └── Parsers/
│ ├── JsonParserTest.php
│ ├── XmlParserTest.php
│ ├── PipedParserTest.php
│ └── JsonpParserTest.php
├── Pest.php # Pest configuration
└── TestCase.php # Base test case
Mock ViaCEP responses in your tests:
use ViaCep\Facades\ViaCep;
use ViaCep\DTO\Address;
test('can create user with address', function () {
// Mock ViaCEP
ViaCep::shouldReceive('cep')
->with('01001000')
->andReturnSelf();
ViaCep::shouldReceive('get')
->andReturn(new Address(
cep: '01001-000',
logradouro: 'Praça da Sé',
complemento: 'lado ímpar',
bairro: 'Sé',
localidade: 'São Paulo',
uf: 'SP',
ibge: '3550308',
gia: '1004',
ddd: '11',
siafi: '7107'
));
// Test your code
$response = $this->post('/users', [
'name' => 'John Doe',
'cep' => '01001000'
]);
$response->assertOk();
});
| Method | Parameters | Return | Description |
|--------|-----------|--------|-------------|
| cep(string $cep) | CEP string | self | Set CEP for search |
| state(string $uf) | State code (2 letters) | self | Set state for address search |
| city(string $city) | City name | self | Set city for address search |
| street(string $street) | Street name (min 3 chars) | self | Set street for address search |
| bulk(array $ceps) | Array of CEPs | self | Set multiple CEPs for bulk search |
| get() | - | Address\|array | Execute the request |
| raw() | - | string | Get raw API response |
| Method | Parameters | Return | Description |
|--------|-----------|--------|-------------|
| format(string\|ResponseFormat) | Format name/enum | self | Set response format |
| asJson() | - | self | Set format to JSON |
| asXml() | - | self | Set format to XML |
| asPiped() | - | self | Set format to Piped |
| asJsonp(string $callback) | Callback name | self | Set format to JSONP |
| jsonp(string $callback) | Callback name | self | Alias for asJsonp |
| Method | Parameters | Return | Description |
|--------|-----------|--------|-------------|
| timeout(int $seconds) | Timeout in seconds | self | Set request timeout |
| retry(int $times) | Number of retries | self | Set retry attempts |
| cache(int $ttl, ?string $key) | TTL in seconds, optional key | self | Enable caching with TTL |
| withoutCache() | - | self | Disable caching for request |
| setBaseUrl(string $url) | Base URL | self | Override API base URL |
| transform(Closure $transformer) | Transformer closure | self | Apply custom transformation |
| Method | Parameters | Return | Description |
|--------|-----------|--------|-------------|
| validate(string $cep) | CEP string | bool | Validate CEP format |
| formatCep(string $cep) | CEP string | string | Format CEP with hyphen |
| clean(string $cep) | CEP string | string | Remove CEP formatting |
CEP (Código de Endereçamento Postal) is the Brazilian postal code system:
00000-000 (8 digits)JSON Format:
{
"cep": "01001-000",
"logradouro": "Praça da Sé",
"complemento": "lado ímpar",
"bairro": "Sé",
"localidade": "São Paulo",
"uf": "SP",
"ibge": "3550308",
"gia": "1004",
"ddd": "11",
"siafi": "7107"
}
XML Format:
<?xml version="1.0" encoding="UTF-8"?>
<xmlcep>
<cep>01001-000</cep>
<logradouro>Praça da Sé</logradouro>
<complemento>lado ímpar</complemento>
<bairro>Sé</bairro>
<localidade>São Paulo</localidade>
<uf>SP</uf>
<ibge>3550308</ibge>
<gia>1004</gia>
<ddd>11</ddd>
<siafi>7107</siafi>
</xmlcep>
Piped Format:
cep:01001-000|logradouro:Praça da Sé|complemento:lado ímpar|bairro:Sé|localidade:São Paulo|uf:SP|ibge:3550308|gia:1004|ddd:11|siafi:7107
Contributions are welcome! Please feel free to submit a Pull Request.
# Clone the repository
git clone https://github.com/eliel-elie/laravel-fluent-viacep.git
# Install dependencies
composer install
# Run tests
composer test
# Run code style fixer
vendor/bin/pint
Please see CHANGELOG for more information on what has changed recently.
If you discover any security-related issues, please email [email protected] instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.
Made with ❤️ for the Laravel community