Database
NativeBlade supports two database strategies that work together:
- SQLite (WASM), Local database inside the app. Offline, instant, persisted to IndexedDB. Used for state, migrations, and local data.
- Native Database, External MySQL, MariaDB, PostgreSQL, or SQLite via Rust bridge. Used for server-side data, shared databases, and enterprise apps.
SQLite (Default)
Works out of the box. No configuration needed.
// Eloquent works normally
$tasks = Task::where('done', false)->get();
Task::create(['title' => 'Buy milk']);
// State management
NativeBlade::setState('user', ['name' => 'John']);
$user = NativeBlade::getState('user');
Migrations run automatically on boot. See Lifecycle.
Native Database
Connect to a real database server using Eloquent, the Rust runtime executes queries via sqlx.
Setup
The native connection is added automatically by nativeblade:install. Configure your credentials in .env:
NB_DB_HOST=127.0.0.1
NB_DB_PORT=3306
NB_DB_DATABASE=myapp
NB_DB_USERNAME=root
NB_DB_PASSWORD=secret
The connection in config/database.php:
'native' => [
'driver' => 'nativeblade-db',
'native_driver' => 'mysql', // mysql, mariadb, pgsql, sqlite
'host' => env('NB_DB_HOST', '127.0.0.1'),
'port' => env('NB_DB_PORT', '3306'),
'database' => env('NB_DB_DATABASE', 'myapp'),
'username' => env('NB_DB_USERNAME', 'root'),
'password' => env('NB_DB_PASSWORD', ''),
'prefix' => '',
],
Usage
Set the connection on your Model:
class Setting extends Model
{
protected $connection = 'native';
protected $fillable = ['key', 'value'];
}
Then use Eloquent normally:
// Read
$settings = Setting::all();
$value = Setting::where('key', 'theme')->value('value');
// Write
Setting::create(['key' => 'language', 'value' => 'en']);
Setting::updateOrCreate(['key' => 'theme'], ['value' => 'dark']);
// Delete
Setting::where('key', 'old_setting')->delete();
// Relationships
$user = User::with('posts.comments')->find(1);
Supported Drivers
native_driver |
Database | Port |
|---|---|---|
mysql |
MySQL 5.7+ | 3306 |
mariadb |
MariaDB 10.5+ | 3306 |
pgsql |
PostgreSQL 12+ | 5432 |
sqlite |
SQLite (native, not WASM) | , |
PostgreSQL Example
// config/database.php
'native' => [
'driver' => 'nativeblade-db',
'native_driver' => 'pgsql',
'host' => env('NB_DB_HOST', '127.0.0.1'),
'port' => env('NB_DB_PORT', '5432'),
'database' => env('NB_DB_DATABASE', 'myapp'),
'username' => env('NB_DB_USERNAME', 'postgres'),
'password' => env('NB_DB_PASSWORD', ''),
],
Native SQLite Example
For a real SQLite file on the filesystem (not WASM):
'native' => [
'driver' => 'nativeblade-db',
'native_driver' => 'sqlite',
'database' => '/path/to/database.sqlite',
],
How It Works
The native database uses a bridge pattern:
PHP: Setting::where('key', 'theme')->get()
→ NativeConnection intercepts the query
→ Serializes SQL + bindings → writes to temp file → exits
→ JS detects pending query → invokes Rust
→ Rust executes via sqlx against the real database
→ Caches result → PHP re-executes
→ NativeConnection finds cached result → returns to Eloquent
Each query triggers one re-execution of PHP. For a page with N queries, there are N+1 PHP executions.
Performance Tips
Use wire:init to avoid blocking navigation while queries execute:
class Settings extends Component
{
public bool $loading = true;
public array $settings = [];
public function loadData()
{
$this->settings = Setting::all()->toArray();
$this->loading = false;
}
}
<div wire:init="loadData">
@if($loading)
<x-nativeblade-skeleton class="h-12 w-full rounded-xl" />
<x-nativeblade-skeleton class="h-12 w-full rounded-xl" />
@else
{{-- Real content --}}
@endif
</div>
Use eager loading to reduce queries:
// Bad: N+1 queries (N+2 PHP executions)
$users = User::all();
foreach ($users as $user) {
$user->posts; // extra query per user
}
// Good: 2 queries (3 PHP executions)
$users = User::with('posts')->get();
HasNativeDatabase Trait
The updateOrCreate and firstOrCreate Eloquent methods do multiple queries internally (SELECT + INSERT/UPDATE), which can cause issues with the bridge cache. Use the HasNativeDatabase trait on your Models to optimize these automatically:
use NativeBlade\Database\HasNativeDatabase;
class Setting extends Model
{
use HasNativeDatabase;
protected $connection = 'native';
protected $fillable = ['key', 'value'];
}
// Now works correctly, uses upsert under the hood
Setting::updateOrCreate(['key' => 'theme'], ['value' => 'dark']);
Setting::firstOrCreate(['key' => 'language'], ['value' => 'en']);
The trait converts updateOrCreate to upsert (single query) when using the native connection. Always add this trait to Models that use protected $connection = 'native'.
Multiple Queries
Multiple queries work correctly as long as they run in the same order on every re-execution. Standard Eloquent code is deterministic and works fine:
// OK, queries run in the same order every time
$users = User::all(); // queryIndex 0
$posts = Post::latest()->take(10)->get(); // queryIndex 1
$stats = Task::where('done', true)->count(); // queryIndex 2
// 3 queries = 4 PHP executions
Relationships work:
// OK, deterministic queries
$user = User::find(1); // queryIndex 0
$posts = $user->posts()->get(); // queryIndex 1
// 2 queries = 3 PHP executions
Avoid non-deterministic logic between queries:
// AVOID, different code paths between re-executions
if (rand(0, 1)) {
$a = User::find(1); // queryIndex 0
} else {
$b = Post::find(1); // queryIndex 0, different SQL, cache mismatch!
}
Transactions
Transactions work within the bridge pattern:
DB::connection('native')->transaction(function () {
Setting::create(['key' => 'a', 'value' => '1']);
Setting::create(['key' => 'b', 'value' => '2']);
});
The Rust side maintains connection pools, so the transaction executes on the same connection.
SQLite vs Native, When to Use What
| Use Case | SQLite (WASM) | Native Database |
|---|---|---|
| Auth state, preferences | ✓ | |
| Offline-first data | ✓ | |
| App cache | ✓ | |
| Shared/team data | ✓ | |
| Server-side database | ✓ | |
| Large datasets | ✓ | |
| Full-text search | ✓ | |
| Complex queries/joins | ✓ | ✓ |
Important Notes
- The default
sqliteconnection always uses the local WASM SQLite,NativeBlade::setState()and migrations always use this - The
nativeconnection uses separate env vars (NB_DB_*) to avoid conflicts - The database server must be accessible from the user’s machine (localhost, LAN, or internet)
- Each query goes through the bridge, use eager loading and
wire:initfor best performance