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
<?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
<?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:

bash
php artisan route:list --path=api

Output yang Dihasilkan:

text
  GET|HEAD       api/ping ................................... routes/api.php:7

                                                            Showing [1] routes

Prefix 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
<?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:

text
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
<?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:

text
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
<?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:

bash
php artisan route:list --path=api/v1/posts

Output yang Dihasilkan:

text
  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] routes

Lima 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 .