Episode sebelumnya saya menutup topik authorization dengan gate dan policy — cara membatasi siapa boleh mengakses apa. Nah, hari ini saya lanjut ke satu arah yang berbeda: bukan menyajikan halaman Blade ke browser, tapi menyajikan data mentah dalam bentuk JSON ke konsumen lain — aplikasi mobile, SPA di frontend, atau bahkan script cron milik klien lain. Ini yang disebut pengembangan API, dan Laravel sudah menyediakan seluruh perangkatnya sejak awal.
Saya sempat salah paham di awal: mengira “API” itu harus proyek terpisah. Ternyata tidak — satu aplikasi Laravel bisa sekaligus melayani halaman web biasa dan endpoint API tinggal di folder yang berbeda. Ini yang saya catat dari proses belajar saya.

Route API: terpisah dari route web
Di proyek Laravel versi lama, route API ada di routes/api.php dengan prefix /api otomatis. Sejak Laravel 11, penulis route dipindah ke bootstrap/app.php, tapi idenya sama: route web (yang mengembalikan view HTML) dan route API (yang mengembalikan data) hidup berdampingan.
// bootstrap/app.php (Laravel 11+)
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)Ini berarti bagi saya: URL endpoint API otomatis dapat awalan /api. Route /api/tasks tidak perlu saya tulis ulang di web.php — tinggal taruh di api.php dan tidak ada risiko ketuker dengan route halaman.
API Resource: supaya JSON tidak berantakan
Kesalahan klasik yang saya buat waktu pertama: mengembalikan model Eloquent langsung dengan return Task::all();. Untuk demo itu jalan, tapi begitu produksi, struktur JSON jadi tidak konsisten dan sulit dikontrol (kolom mana yang bocor ke publik, mana yang disembunyikan). Solusinya adalah API Resource — semacam “pembungkus” yang menentukan bentuk akhir JSON.
php artisan make:resource TaskResource
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class TaskResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'is_done' => (bool) $this->is_done,
'due_date' => $this->due_date?->toDateString(),
'created_at' => $this->created_at->toIso8601String(),
];
}
}Kemudian controller API tinggal menggunakannya, lengkap dengan pagination supaya klien tidak kewalahan menerima ribuan baris sekaligus:
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\TaskResource;
use App\Models\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index(Request $request)
{
$tasks = Task::where('user_id', $request->user()->id)
->latest('due_date')
->paginate(20);
return TaskResource::collection($tasks);
}
}Sanctum: token sebagai pengganti sesi login
Di halaman web, Laravel mengenali pengguna lewat sesi cookie — browser otomatis membawa cookie tiap request. Aplikasi mobile tidak punya perilaku itu. Maka dikenal mekanisme token: klien menyimpan satu string, lalu mengirimkannya di setiap request melalui header Authorization: Bearer xxx.
Laravel membungkusnya dalam paket bernama Laravel Sanctum. Cara memasangnya cukup satu perintah Composer, lalu daftarkan middleware-nya di route:
composer require laravel/sanctum php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
// routes/api.php
use App\Http\Controllers\Api\TaskController;
Route::middleware('auth:sanctum')->group(function () {
Route::get('/tasks', [TaskController::class, 'index']);
Route::post('/tasks', [TaskController::class, 'store']);
});Bagian auth:sanctum inilah “pagi kedua” yang saya sebut di episode authorization: kalau token tidak valid atau tidak ada, request langsung ditolak dengan status 401 Unauthorized sebelum sampai ke controller. Prinsipnya sama dengan gate — cek dulu, baru kerjakan.
Untuk membuat token (misalnya saat pengguna melakukan login dari aplikasi mobile), Sanctum menyediakan method createToken:
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
return response()->json(['message' => 'Kredensial salah'], 422);
}
$token = $user->createToken('mobile-app')->plainTextToken;
return response()->json(['token' => $token]);Perhatikan plainTextToken — inilah satu-satunya momen token mentah bisa dilihat. Setelah dikirim ke klien, Laravel hanya menyimpan hash-nya di database. Kalau token hilang, klien tinggal bikin baru. Ini berarti bagi saya: tidak perlu menyimpan password pengguna di sisi klien sama sekali.
Bedanya controller web dan controller API
Saya membuat catatan perbandingan kecil supaya tidak ketuker saat menulis kode:
| Aspek | Controller Web | Controller API |
|---|---|---|
| Response | View Blade / redirect | JSON (ApiResource) |
| Autentikasi | Sesi + cookie | Token Sanctum |
| Validasi gagal | Redirect balik + error di session | JSON error 422 |
| State | Menyimpan data di session | Stateless, tiap request mandiri |
Komentar Terbaru