Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Nusa Laravel Package

creasi/laravel-nusa

creasi/laravel-nusa brings Indonesia's administrative region data into Laravel apps with ready-to-use Eloquent models, migrations, and seeders for provinces, regencies, districts, and villages, making reliable local location data quick and easy to add.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish only what you need

composer require creasi/laravel-nusa

php artisan vendor:publish --tag=creasi-config      # config/nusa.php -> config/creasi/nusa.php
php artisan vendor:publish --tag=creasi-migrations  # optional address tables
php artisan vendor:publish --tag=creasi-lang        # optional translations

The package auto-registers a separate SQLite connection named nusa pointing at the pre-compiled database/nusa.sqlite shipped in the package. There is no import, migration, or seed step required to start querying — that is the main reason to pick this over DIY region tables.

Where to look first

  1. config/nusa.php (published to config/creasi/nusa.php) — connection name, route prefix, and any table/model overrides.
  2. routes/nusa.php — the HTTP API is loaded automatically under the nusa prefix; read this before exposing anything.
  3. src/Models — Province, Regency, District, Village with the hierarchy wired up.
  4. resources/static — pre-generated CSV/JSON/GeoJSON if you'd rather ship data to the frontend than query.
  5. The VitePress docs (English, mirrored in id/) — better than the README for relation and API details.

First use case: a cascading address picker

use Creasi\Nusa\Models\Province; // verify namespace against src/Models

$provinces = Province::orderBy('name')->get(['code', 'name']);

// code is the string primary key, never an auto-increment id
$regencies = Province::find('32')->regencies()->orderBy('name')->get(['code', 'name']);

The models are read-only reference data: string code primary keys, non-incrementing, no timestamps. Route model binding already resolves by code, so Route::get('/regency/{regency}', ...) works without a getRouteKeyName() override.

A quick sanity check that the connection is live:

php artisan tinker
>>> DB::connection('nusa')->table('provinces')->count();

If that fails, the SQLite file isn't readable from your deployment path or the connection config wasn't published — start there, not with model code.

Implementation Patterns

Reference data, joined from your own tables

Store the code values on your own models (province_code, regency_code, district_code, village_code) and relate across the connection boundary:

// app/Models/Address.php
public function province()
{
    return $this->belongsTo(\Creasi\Nusa\Models\Province::class, 'province_code', 'code');
}

Store codes, not names. Region names change (renames and pemekaran splits happen upstream), and codes are the join key the package guarantees. Denormalise a display name only as a cache you're willing to rebuild.

Validation with cross-connection exists

Laravel's exists rule accepts connection.table, which is exactly what a separate SQLite connection needs:

'province_code' => ['required', 'string', Rule::exists('nusa.provinces', 'code')],
'regency_code'  => ['required', 'string', Rule::exists('nusa.regencies', 'code')],

Pair this with a Rule::exists('nusa.regencies', 'code')->where('province_code', $request->province_code) so a client can't submit a mismatched province/regency pair. Never trust the browser cascade alone.

Dropdowns: cache the whole tree, don't query per keystroke

Provinces/regencies/districts change on the order of months, not seconds. Load them once into cache as plain arrays and serve autocomplete from that:

$regions = Cache::rememberForever('nusa.regencies', fn () =>
    Regency::orderBy('name')->get(['code', 'province_code', 'name'])->toArray()
);

Invalidate on deploy if you refresh the underlying data. This also sidesteps SQLite's single-writer/limited-concurrency behaviour under load.

Two integration surfaces — choose deliberately

  • Eloquent (server-side): best for validation, joins, exports, and admin filters. Zero HTTP overhead, full relation graph.
  • HTTP API (/nusa/...): useful for a separate frontend or a non-PHP service. Check routes/nusa.php for the exact endpoints and, importantly, the middleware stack before you ship it.
  • Static JSON/GeoJSON: best for map layers and client-side autocomplete where you don't want a round trip. Regenerate with the package's nusa:generate-static command if you're working inside the package repo; otherwise consume the published files.

A common split: server-side validation via Eloquent, client-side pickers via the static JSON, and the API only for ad-hoc lookups.

Coordinates and maps

Records carry coordinates, which makes the models useful for distance sorting and map centring without a second geocoding service. If you don't need them, the package's nusa:dist command produces a distribution database without coordinates — smaller and faster to ship, but it permanently removes that capability from the copy, so decide early.

Testing

Point tests at fixtures or a cached copy rather than hammering the packaged SQLite file. Guard against RefreshDatabase-style resets targeting the nusa connection — you want to migrate your schema, not rebuild reference data you don't own. A small helper that returns a fixed province/regency pair keeps feature tests fast and independent of upstream data churn.

Upgrades

Pin the version. The package targets PHP ≥ 8.2 and Laravel 9–13, and with ~101 stars and zero dependents you are effectively the integration test. Read the changelog for data refreshes, since a routine release can move codes you've stored.

Gotchas and Tips

Two connections means no real foreign keys. You cannot add a database-level FOREIGN KEY from your addresses table to nusa.regencies — SQLite (and cross-connection constraints generally) won't enforce it. Integrity is entirely on your validation rules and application code. Write a test that asserts every stored regency_code still resolves after a data refresh.

Transactions don't span connections. A DB::transaction() on your default connection won't roll back anything touching nusa. That's fine because you should never write to the reference tables, but it matters if you build an import/backfill job that reads and writes in the same loop.

Config path trap. Publishing moves config/nusa.php to config/creasi/nusa.php. Depending on how the service provider registers it, you may be reading config('creasi.nusa.*') rather than config('nusa.*'). Check the published file and the provider before debugging "my config change does nothing" — this is the single most likely time-waster in the first hour.

code is a string, and JavaScript will ruin it. Province/regency codes with leading zeros passed through parseInt, numeric form inputs, or a JSON schema that infers integers will silently break lookups. Keep them as strings end to end, and use string columns in your migrations. Same applies to route model binding: find(32) won't match '32' reliably depending on driver coercion — always pass strings.

Don't run the maintainer commands in your app. composer upstream:up, nusa:import, and the Docker workflow documented in AGENTS.md are for contributing to the package, not for consuming it. The database is already compiled. Reaching for those on a fresh install is the most common onboarding mistake; they require the package repo and a Docker daemon.

Decide about the published address migrations. The creasi-migrations tag creates address tables in your database. Review them before running — if you already have an addresses table, or you want extra columns (label, recipient, geohash), skip the publish and model your own schema around the code columns. The reference data itself is not created by migrations.

Route prefix collisions and exposure. Routes load automatically under nusa. If you already own that prefix, or you don't want a public unauthenticated region API, adjust it in config or exclude the routes. Run php artisan route:list | grep nusa after install and again after route:cache — automatic route loading is easy to forget when auditing your public surface.

Read-only discipline. Treat nusa.* as immutable. Any attempt to seed, patch, or "fix" a region locally will be blown away on the next package update and will diverge from upstream codes. Corrections belong upstream (the data comes from cahyadsn's wilayah and postal-code sources), or in your own overlay table.

Deployment and file paths. The SQLite file lives under the package's database/ directory. Ensure it's readable by the PHP user, isn't excluded by a vendor pruning step in your deploy, and isn't on a filesystem that blocks SQLite locking (some network mounts). For high-read production traffic, prefer cached arrays over live queries.

Debugging checklist, in order: DB::connection('nusa')->getPdo() → table count → config('database.connections.nusa') → published config key. Coordinates present? Confirm you didn't deploy the nusa:dist variant. Empty relations? Check you're filtering by code, not id.

Extension points are limited by design. You get config, routes, lang, and optional migrations — not a documented model-swap or repository contract. If you need custom behaviour (search ranking, alternate labels, caching decorators), wrap the models in your own service class rather than extending them; that keeps you insulated if internal namespaces shift between releases.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource