Laravel API Resources Pagination Query Scopes JSON Responses
Definisi
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.
Kapan resource dipakai
Pakai resource ketika kamu ingin mengendalikan bentuk output API, bukan sekadar mengirim model mentah.
Resource cocok untuk:
- menyembunyikan field yang tidak perlu atau sensitif
- merapikan nama field agar konsisten dengan kontrak API
- menambahkan data turunan seperti
is_active,display_name, atauformatted_date - menjaga response tetap stabil walaupun struktur model berubah
- membuat format response list dan detail tetap seragam
Contoh 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.
Kapan tidak perlu memaksa resource
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.
Cara kerja resource
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' => $this->title,
'slug' => $this->slug,
'body' => $this->body,
'author' => $this->whenLoaded('user', function () {
return [
'id' => $this->user->id,
'name' => $this->user->name,
];
}),
];
}
}Pola di atas menunjukkan dua hal penting:
- resource menentukan bentuk output
- relasi sebaiknya sudah dimuat lebih dulu kalau akan dipakai di resource
Kalau relasi belum di-load, resource bisa memicu query tambahan tanpa kamu sadari.
Pagination bekerja bagaimana
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 stabil
paginate()
$posts = Post::query()->latest()->paginate(10);paginate() biasanya mengirim:
- data item per halaman
- informasi total data
- jumlah halaman
- halaman aktif
- link navigasi
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.
Bentuk response pagination
Kalau pagination dibungkus resource collection, Laravel biasanya menambahkan metadata pagination secara otomatis.
Contoh:
return PostResource::collection(
Post::query()->latest()->paginate(10)
);Output umumnya berisi:
datalinksmeta
Struktur detail bisa disesuaikan, tetapi inti dasarnya sama: data dipisah dari metadata navigasi.
Query scopes merapikan query
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:
- local scope untuk query khusus model tertentu
- global scope untuk aturan yang otomatis berlaku di semua query model
Local scope
use Illuminate\Database\Eloquent\Builder;
class Post extends Model
{
public function scopePublished(Builder $query): Builder
{
return $query->whereNotNull('published_at');
}
public function scopeSearch(Builder $query, ?string $term): Builder
{
return $query->when($term, function (Builder $query, string $term) {
$query->where(function (Builder $query) use ($term) {
$query->where('title', 'like', "%{$term}%")
->orWhere('body', 'like', "%{$term}%");
});
});
}
}Pemakaian:
$posts = Post::query()
->published()
->search(request('q'))
->latest()
->paginate(10);Keuntungan utamanya:
- query lebih mudah dibaca
- aturan berulang tidak disalin ke banyak controller
- perubahan logika filter cukup dilakukan di satu tempat
Global scope
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 query → resource → response
Alur yang sehat biasanya seperti ini:
- controller menerima request
- scope menyusun query yang relevan
- query dieksekusi
- pagination membatasi hasil
- resource mengubah output
- JSON response dikirim ke client
Kalau urutan ini dijaga, controller tidak jadi tempat semua logika bercampur.
JSON responses yang rapi
Untuk API, response JSON sebaiknya konsisten dari sisi struktur dan status code.
Response sukses
return response()->json([
'message' => 'Data berhasil diambil',
'data' => PostResource::collection($posts),
]);Response daftar dengan pagination
return PostResource::collection(
Post::query()->published()->paginate(10)
);Response detail
return new PostResource($post);Response error
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.
Jebakan umum saat membangun API
1. Mengembalikan model mentah langsung
Kalau kamu mengembalikan model atau collection mentah, response bisa ikut membawa field yang tidak direncanakan.
Risikonya:
- field sensitif bocor
- kontrak API berubah tanpa sadar
- client jadi tergantung pada struktur model internal
Resource membantu mencegah ini.
2. Menaruh query berat di controller
Controller yang penuh query sulit dibaca dan sulit diuji. Lebih sehat jika query umum dipindahkan ke scope atau service.
3. Melakukan pagination setelah collection diubah ke memory
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);4. Memicu N+1 dari resource
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);5. Struktur JSON tidak konsisten
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.
6. Memakai scope global tanpa dokumentasi
Scope global memang nyaman, tetapi efeknya tersembunyi. Kalau dipakai, pastikan tim paham bahwa query model itu sudah dipersempit dari awal.
7. Pagination metadata tidak dipikirkan
Untuk UI list, client biasanya butuh total halaman, current page, dan link navigasi. Jangan hanya kirim data item kalau client sebenarnya membutuhkan navigasi.
Pola implementasi yang sehat
Pola yang umum dan aman untuk API Laravel:
- ambil request di controller
- susun query lewat scope
- eager load relasi yang dipakai resource
- jalankan pagination
- kirim hasil lewat resource
- bungkus response JSON dengan status code yang sesuai
Contoh lengkap:
public function index(Request $request)
{
$posts = Post::query()
->published()
->search($request->string('q'))
->with('user')
->latest()
->paginate(10);
return PostResource::collection($posts);
}Referensi singkat
- Resource dipakai untuk mengontrol bentuk output API.
- Pagination dipakai untuk membatasi jumlah data dan menambah metadata navigasi.
- Query scope dipakai untuk merapikan query yang berulang.
- JSON response dipakai untuk menyusun kontrak API yang konsisten.
- Relasi yang dipakai resource sebaiknya di-eager load lebih dulu.
- Jangan biarkan controller menjadi tempat semua query dan transformasi menumpuk.