Kita sudah memegang prinsip REST; sekarang saatnya menerjemahkannya ke mekanisme routing Laravel. Kabar baiknya, framework ini sudah menyiapkan dunia terpisah untuk API: file routes/api.php, prefix otomatis, dan middleware group yang khusus stateless. Chapter ini membedah ketiganya satu per satu, lalu menutup dengan dua pola yang wajib dikuasai setiap pembuat API: versioning lewat path prefix dan Route::apiResource() untuk endpoint CRUD lengkap dalam satu baris.
routes/api.php dan withRouting #
Sejak Laravel 11, aplikasi baru hanya membawa routes/web.php dan routes/console.php. File routes/api.php hadir begitu kita mendaftarkannya lewat withRouting() di bootstrap/app.php:
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
})
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->shouldRenderJsonWhen(
fn (Request $request) => $request->is('api/*') || $request->expectsJson(),
);
})->create();Argumen api: memberi tahu Laravel bahwa semua route di file tersebut adalah route API. Blok withExceptions() di atas juga layak dicermati: konfigurasi ini memaksa semua exception di path api/* dijawab sebagai JSON — konvensi yang dipakai sandbox Course ini agar error API selalu konsisten, terlepas dari header Accept yang dikirim client.
Prefix /api Otomatis #
Setiap route di routes/api.php otomatis mendapat prefix /api tanpa kita menulis Route::prefix('api') sedikit pun:
<?php
// routes/api.php
use Illuminate\Support\Facades\Route;
Route::get('/ping', fn (): array => ['message' => 'pong', 'versi' => 'v1']);Route di atas bukan /ping, melainkan /api/ping — perbedaan nyata yang bisa kita buktikan lewat perintah artisan:
php artisan route:list --path=apiOutput yang Dihasilkan:
GET|HEAD api/ping ................................... routes/api.php:7
Showing [1] routesPrefix ini dipasang oleh RouteServiceProvider-nya framework ketika memuat file, jadi URL kita tetap pendek di kode namun terpisah rapi dari route web.
Middleware Group api #
Inilah pembeda terbesar dua dunia tersebut. Route API berjalan di middleware group api, sedangkan route web di group web. Kita bedah isinya dengan runner singkat yang membaca konfigurasi router:
<?php
// lihat-middleware.php
$dir = $argv[1];
require $dir.'/vendor/autoload.php';
$app = require $dir.'/bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
print_r($app->make('router')->getMiddlewareGroups()['api']);Output yang Dihasilkan:
Array
(
[0] => Illuminate\Routing\Middleware\SubstituteBindings
)Group api bawaan Laravel 13 hanya berisi SubstituteBindings — middleware yang mengubah parameter {post} menjadi model lewat route model binding. Bandingkan dengan group web yang memuat enam middleware: EncryptCookies, AddQueuedCookiesToResponse, StartSession, ShareErrorsFromSession, PreventRequestForgery (proteksi CSRF), dan SubstituteBindings. Tidak ada session dan tidak ada CSRF di group api — persis implementing statelessness yang kita bahas di chapter sebelumnya.
Versioning lewat Prefix #
API berubah lebih cepat daripada halaman web karena client-nya terpasang di aplikasi orang lain. Karena itu endpoint API lazim di-version-kan lewat prefix path. Polanya sederhana — bungkus route per versi:
<?php
// routes/api.php
use Illuminate\Support\Facades\Route;
Route::prefix('v1')->group(function (): void {
Route::get('/ping', fn (): array => ['message' => 'pong', 'versi' => 'v1']);
});
Route::prefix('v2')->group(function (): void {
Route::get('/ping', fn (): array => ['message' => 'pong', 'versi' => 'v2']);
});Kita buktikan ketiganya dengan runner dari chapter sebelumnya — request ber-versioning ditemukan, sedangkan tanpa versi gagal:
Output yang Dihasilkan:
GET /api/v1/ping → HTTP 200 {"message":"pong","versi":"v1"}
GET /api/v2/ping → HTTP 200 {"message":"pong","versi":"v2"}
GET /api/ping → HTTP 404 {
"message": "The route api/ping could not be found."
}Client lama yang memanggil /api/v1/... tidak tersentuh ketika kita merilis perilaku baru di /api/v2/... — kontrak lama tetap terjaga. Strategi deprecation yang menyertainya akan kita bahas di chapter API Versioning
.
Route::apiResource #
Untuk resource CRUD lengkap, Route::apiResource() mendaftarkan lima endpoint sekaligus. Kita butuh controller bernama Api/PostController — kerangkanya bisa dibuat dengan php artisan make:controller Api/PostController --api --model=Post (kita bedah isi controller di chapter API Resource Controllers
):
<?php
// routes/api.php
use App\Http\Controllers\Api\PostController;
use Illuminate\Support\Facades\Route;
Route::prefix('v1')->group(function (): void {
Route::get('/ping', fn (): array => ['message' => 'pong', 'versi' => 'v1']);
Route::apiResource('posts', PostController::class);
});route:list memperlihatkan kelima route beserta nama dan pasangan controller-method-nya:
php artisan route:list --path=api/v1/postsOutput yang Dihasilkan:
GET|HEAD api/v1/posts ........ posts.index › Api\PostController@index
POST api/v1/posts ........ posts.store › Api\PostController@store
GET|HEAD api/v1/posts/{post} ... posts.show › Api\PostController@show
PUT|PATCH api/v1/posts/{post} posts.update › Api\PostController@update
DELETE api/v1/posts/{post} posts.destroy › Api\PostController@dest…
Showing [5] routesLima route ini persis mengikuti pemetaan REST: GET untuk index dan show, POST untuk store, PUT|PATCH untuk update, DELETE untuk destroy. Dibanding Route::resource(), versi api tidak membuat route create dan edit — dua method itu hanya relevan untuk menampilkan form HTML di web.
Routing kini rapi: prefix otomatis, group middleware stateless, versi terkelola, dan endpoint CRUD terdaftar lengkap. Rute yang tersisa adalah isi jawabannya — bentuk JSON yang konsisten dari setiap endpoint, topik chapter JSON Responses . Dokumentasi lengkapnya tersedia di dokumentasi routing .