Laravel API Resources Pagination Query Scopes JSON Responses
Selesai membaca dokumentasi ini?
Kembali ke rujukan utama Laravel untuk melanjutkan topik lainnya.
Kembali ke rujukan utama Laravel untuk melanjutkan topik lainnya.
Di Laravel, API Resource dipakai untuk mengubah model atau collection menjadi bentuk JSON yang stabil dan mudah dikontrol. Resource bertindak sebagai lapisan transformasi antara data Eloquent dan response API.
Pagination dipakai untuk memecah hasil query yang besar menjadi halaman-halaman kecil supaya response tetap ringan dan client bisa meminta data secara bertahap.
Query scope dipakai untuk membungkus pola query yang sering dipakai ke dalam method model, supaya query controller lebih rapi, konsisten, dan mudah dibaca ulang.
JSON response adalah format output yang paling umum untuk API. Di Laravel, response JSON sebaiknya dibentuk secara konsisten: status code jelas, struktur data konsisten, dan pesan error mudah diprediksi.
Pakai resource ketika kamu ingin mengendalikan bentuk output API, bukan sekadar mengirim model mentah.
Resource cocok untuk:
is_active, display_name, atau formatted_dateContoh sederhana:
use App\Http\Resources\PostResource;
use App\Models\Post;
Route::get('/posts/{post}', function (Post $post) {
return new PostResource($post);
});Kalau kebutuhanmu hanya mengembalikan field mentah untuk endpoint internal yang sangat sederhana, resource masih boleh dipakai, tetapi nilainya paling terasa saat API mulai tumbuh dan response harus konsisten.
Resource tidak wajib untuk semua kasus. Untuk response kecil dan satu kali pakai, response()->json([...]) bisa cukup.
Namun, begitu response mulai dipakai di banyak endpoint, atau ada aturan field yang perlu dijaga, resource biasanya lebih sehat daripada membangun array manual di controller.
Resource menerima model atau collection, lalu mengubahnya lewat method toArray().
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'title'
Pola di atas menunjukkan dua hal penting:
Kalau relasi belum di-load, resource bisa memicu query tambahan tanpa kamu sadari.
Pagination membatasi jumlah item yang dikirim dalam satu response. Tujuannya bukan hanya UX, tetapi juga biaya query dan ukuran payload.
Di Laravel, pagination umum dipakai lewat:
paginate() untuk pagination lengkap dengan metadata dan linksimplePaginate() untuk kasus yang tidak butuh total countcursorPaginate() untuk dataset besar dan urutan data yang stabilpaginate()$posts = Post::query()->latest()->paginate(10);paginate() biasanya mengirim:
Ini cocok untuk halaman list yang butuh informasi jumlah total secara akurat.
simplePaginate()$posts = Post::query()->latest()->simplePaginate(10);simplePaginate() lebih ringan karena tidak selalu menghitung total record secara penuh. Cocok kalau client hanya perlu tombol berikutnya dan sebelumnya.
cursorPaginate()$posts = Post::query()->orderBy('id')->cursorPaginate(10);cursorPaginate() cocok untuk data besar atau feed yang terus bertambah. Pendekatan ini menghindari beberapa biaya offset besar yang sering mahal pada dataset besar.
Kalau pagination dibungkus resource collection, Laravel biasanya menambahkan metadata pagination secara otomatis.
Contoh:
return PostResource::collection(
Post::query()->latest()->paginate(10)
);Output umumnya berisi:
datalinksmetaStruktur detail bisa disesuaikan, tetapi inti dasarnya sama: data dipisah dari metadata navigasi.
Query scope dipakai untuk menyimpan query yang berulang di model. Hasilnya, controller lebih pendek dan intent query lebih jelas.
Ada dua jenis yang sering dipakai:
use Illuminate\Database\Eloquent\Builder;
class Post extends Model
{
public function scopePublished(Builder $query): Builder
{
return $query->whereNotNull('published_at'
Pemakaian:
$posts = Post::query()
->published()
->search(request('q'))
->latest()
->paginate(10);Keuntungan utamanya:
Global scope cocok untuk aturan yang memang harus selalu berlaku, misalnya data tenant tertentu atau data yang hanya boleh menampilkan record aktif.
Tetapi scope global harus dipakai hati-hati karena efeknya tersembunyi. Kalau terlalu agresif, developer lain bisa bingung kenapa data “hilang” dari query.
Alur yang sehat biasanya seperti ini:
Kalau urutan ini dijaga, controller tidak jadi tempat semua logika bercampur.
Untuk API, response JSON sebaiknya konsisten dari sisi struktur dan status code.
return response()->json([
'message' => 'Data berhasil diambil',
'data' => PostResource::collection($posts),
]);return PostResource::collection(
Post::query()->published()->paginate(10)
);return new PostResource($post);return response()->json([
'message' => 'Post tidak ditemukan',
], 404);Hal yang penting di sini bukan cuma format, tetapi juga konsistensi. Client API biasanya lebih nyaman kalau pola responsnya bisa ditebak.
Kalau kamu mengembalikan model atau collection mentah, response bisa ikut membawa field yang tidak direncanakan.
Risikonya:
Resource membantu mencegah ini.
Controller yang penuh query sulit dibaca dan sulit diuji. Lebih sehat jika query umum dipindahkan ke scope atau service.
Ini jebakan yang sering terjadi:
$posts = Post::all()->take(10);Masalahnya, all() mengambil semua data dulu baru dipotong. Untuk data besar, pola ini boros.
Lebih tepat:
$posts = Post::paginate(10);Kalau resource membaca relasi yang belum di-load, bisa muncul query tambahan di dalam loop.
Contoh aman:
$posts = Post::with('user')->paginate(10);
return PostResource::collection($posts);Kalau satu endpoint mengembalikan data, endpoint lain mengembalikan result, dan endpoint lain lagi mengembalikan array mentah, client akan lebih sulit diintegrasikan.
Pilih pola yang konsisten lalu pakai terus.
Scope global memang nyaman, tetapi efeknya tersembunyi. Kalau dipakai, pastikan tim paham bahwa query model itu sudah dipersempit dari awal.
Untuk UI list, client biasanya butuh total halaman, current page, dan link navigasi. Jangan hanya kirim data item kalau client sebenarnya membutuhkan navigasi.
Pola yang umum dan aman untuk API Laravel:
Contoh lengkap:
public function index(Request $request)
{
$posts = Post::query()
->published()
->search($request->string('q'))
->with('user')