Arsitektur aplikasi NativePHP dengan backend Laravel: autentikasi token via Sanctum, strategi delta sync, conflict resolution sederhana, optimis updates, dan sinkronisasi data offline-first ke backend sungguhan.

Setelah di episode 14 kita mempelajari plugin architecture dan custom plugin, pada episode ini kita masuk ke integrasi dengan backend: menghubungkan aplikasi NativePHP di perangkat dengan API Laravel di server. Di dunia nyata, hampir semua aplikasi mobile membutuhkan backend — untuk autentikasi, penyimpanan data permanen, notifikasi, dan sinkronisasi lintas perangkat.
Kunci di sini adalah offline-first sync: aplikasi berfungsi tanpa internet, data disinkronkan ke server saat koneksi tersedia, dan conflict ditangani dengan strategi yang jelas.
Sanctum menyediakan autentikasi token sederhana untuk mobile app. Karena NativePHP berjalan di perangkat (bukan browser), gunakan token-based authentication:
<?php
use Illuminate\Support\Facades\Http;
$login = Http::post('https://api.myapp.com/auth/login', [
'email' => $email,
'password' => $password,
]);
if ($login->successful()) {
$token = $login->json('token');
\Native\Laravel\Facades\SecureStorage::put('auth_token', $token);
}<?php
use Illuminate\Support\Facades\Http;
function apiRequest()
{
$token = \Native\Laravel\Facades\SecureStorage::get('auth_token');
return Http::withToken($token)
->timeout(30)
->retry(3, 1000);
}<?php
use Illuminate\Support\Facades\Http;
$refresh = Http::withToken($token)
->post('https://api.myapp.com/auth/refresh');
if ($refresh->successful()) {
$newToken = $refresh->json('token');
\Native\Laravel\Facades\SecureStorage::put('auth_token', $newToken);
}Delta sync mengirim hanya perubahan (delta), bukan seluruh dataset. Ini hemat bandwidth dan lebih cepat.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Note extends Model
{
protected $fillable = ['title', 'content', 'synced_at'];
// Record yang belum disinkronkan
public function scopeUnsynced($query)
{
return $query->whereNull('synced_at');
}
// Record yang dihapus lokal (soft delete untuk sync)
public function scopeDeletedUnsynced($query)
{
return $query->whereNotNull('deleted_at')
->whereNull('deleted_synced_at');
}
}<?php
use App\Models\Note;
use Illuminate\Support\Facades\Http;
class SyncService
{
public function pushChanges(): void
{
$token = \Native\Laravel\Facades\SecureStorage::get('auth_token');
$unsynced = Note::unsynced()->get();
foreach ($unsynced->chunk(50) as $chunk) {
$response = Http::withToken($token)
->post('https://api.myapp.com/sync/push', [
'notes' => $chunk->toArray(),
]);
if ($response->successful()) {
$ids = $chunk->pluck('id');
Note::whereIn('id', $ids)->update([
'synced_at' => now(),
]);
}
}
}
public function pullChanges(): void
{
$token = \Native\Laravel\Facades\SecureStorage::get('auth_token');
$lastSync = \Native\Laravel\Facades\SecureStorage::get('last_sync_at');
$response = Http::withToken($token)
->get('https://api.myapp.com/sync/pull', [
'since' => $lastSync,
]);
if ($response->successful()) {
foreach ($response->json('notes') as $noteData) {
Note::updateOrCreate(
['id' => $noteData['id']],
[
'title' => $noteData['title'],
'content' => $noteData['content'],
'synced_at' => now(),
]
);
}
\Native\Laravel\Facades\SecureStorage::put(
'last_sync_at',
now()->toIso8601String()
);
}
}
}Conflict terjadi ketika record yang sama diubah di perangkat DAN di server sebelum sync terjadi. Strategi umum:
<?php
// Server menerima data dengan timestamp
$serverNote = Note::find($noteData['id']);
$clientTimestamp = Carbon::parse($noteData['updated_at']);
$serverTimestamp = Carbon::parse($serverNote->updated_at);
if ($clientTimestamp->gt($serverTimestamp)) {
// Client lebih baru — update server
$serverNote->update($noteData);
} else {
// Server lebih baru — kirim ke client
return $serverNote->toArray();
}Optimistic updates memperbarui UI sebelum konfirmasi dari server — user tidak merasa ada delay:
<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Note;
class TaskList extends Component
{
public function toggleComplete($noteId)
{
// Update lokal dulu (optimistic)
$note = Note::find($noteId);
$note->update([
'completed' => !$note->completed,
'synced_at' => null, // tandai perlu sync
]);
// Trigger background sync
\App\Jobs\SyncNotes::dispatch();
}
}Warning
Optimistic updates bisa menyebabkan inkonsistensi sementara. Jika server menolak update, UI harus di-revert. Implementasikan rollback mechanism untuk kasus error.
Inti yang harus dibawa pulang:
unsynced records), bukan seluruh dataset.Di episode 16 selanjutnya, kita akan membahas Development Loop: Hot Reload & Debugging — hot reload port yang bisa dikonfigurasi, real-device hot reload iOS, native:debug command, log streaming, dan integrasi Xcode/Android Studio untuk debugging. Sampai jumpa di episode 16!