Belajar Laravel #18: API Development — Route, Resource, dan Token Sanctum

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.

Diagram alur request API Laravel: client dengan Bearer token, middleware auth:sanctum, controller, response JSON
Alur request API: token dibawa di header, diverifikasi Sanctum, lalu controller mengembalikan JSON.

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:

Baris “stateless” itu yang paling mengubah cara berpikir saya. Karena API tidak menyimpan sesi di server, setiap request harus membawa identitasnya sendiri (token). Konsekuensinya: skala jadi lebih mudah, tapi kita harus disiplin soal keamanan token — jangan pernah kirim lewat URL, karena URL sering tercatat di log server.

Yang saya rangkum

API development di Laravel menurut saya bukan topik baru yang menakutkan — ini hanya “menghidangkan” model yang sudah saya kenal dari episode migration dan Eloquent, tapi lewat piringan yang berbeda (JSON, bukan HTML). Tiga hal yang saya pegang: route API dipisah dari web, bentuk JSON dikontrol lewat Resource, dan autentikasi mobile pakai token Sanctum alih-alih cookie. Di episode berikutnya saya akan masuk ke testing — memastikan endpoint seperti ini benar-benar bekerja sebelum diumumkan ke klien.

Sumber

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *

Situs ini menggunakan Akismet untuk mengurangi spam. Pelajari bagaimana data komentar Anda diproses

© 2026 Catatan Rudy

AspekController WebController API
ResponseView Blade / redirectJSON (ApiResource)
AutentikasiSesi + cookieToken Sanctum
Validasi gagalRedirect balik + error di sessionJSON error 422
StateMenyimpan data di sessionStateless, tiap request mandiri