Route API sudah berdiri, tetapi API baru benar-benar nyaman dipakai ketika bentuk jawabannya konsisten. Client yang harus menebak apakah data ada di root JSON atau di dalam data, atau harus mem-parsing error dalam lima bentuk berbeda, adalah client yang cepat frustrasi. Chapter ini membahas disiplin membentuk response JSON di Laravel: helper response()->json(), konversi otomatis dari array, pola envelope untuk sukses dan error, sampai perilaku karakter Unicode yang sering mengejutkan. Dasar helper JSON pernah kita singgung di chapter Response JSON
; sekarang kita angkat ke level desain API.
response()->json: Data, Status, Header #
Helper response()->json() menerima tiga argumen: data di posisi pertama, status code di kedua, dan array header di ketiga. Ketiganya sekaligus terlihat pada endpoint yang membuat resource baru:
<?php
// routes/api.php
use Illuminate\Support\Facades\Route;
Route::post('/dengan-header', fn () => response()->json(
['status' => 'dibuat'],
201,
['X-Rate-Limit' => '60'],
));Runner dari Konsep REST API memperlihatkan ketiganya bekerja sama:
Output yang Dihasilkan:
status : 201
header : X-Rate-Limit=60
body : {"status":"dibuat"}Status 201 di argumen kedua bukan dekorasi — client membacanya untuk tahu bahwa resource baru saja lahir, bahkan sebelum membaca body. Header tambahan seperti Location atau informasi rate limit ikut terpasang tanpa rantai method tambahan.
Array Otomatis Menjadi JSON #
Untuk controller sederhana, Laravel bahkan mempersingkat lebih jauh: array yang dikembalikan dari route atau controller otomatis dikonversi menjadi response JSON lengkap dengan header Content-Type: application/json:
<?php
// routes/api.php
use App\Models\Post;
use Illuminate\Support\Facades\Route;
Route::get('/ringkasan', fn (): array => [
'data' => ['total_post' => Post::count()],
'meta' => ['versi' => '1.0'],
]);Output yang Dihasilkan:
status : 200
Content-Type : application/json
body : {"data":{"total_post":1},"meta":{"versi":"1.0"}}Bentuk yang dipretty-print agar mudah dibaca:
{
"data": {
"total_post": 1
},
"meta": {
"versi": "1.0"
}
}Envelope: Kontrak Bentuk Jawaban #
Endpoint /ringkasan di atas sengaja memakai pola envelope: data selalu berada di kunci data, sementara informasi di luar data (versi, pagination, total) berada di meta. Pola ini menjawab masalah klasik desain API — ketika kita perlu menambahkan informasi di luar data, kunci data yang sudah baku tinggal dipertahankan, dan client lama tidak patah. Prinsipnya satu untuk seluruh API:
| Situasi | Bentuk | Status |
| | | |
| Sukses berisi data | {"data": ..., "meta": ...} | 200 / 201 |
| Sukses tanpa isi | body kosong | 204 |
| Kesalahan client | {"message": ..., "errors": ...} | 4xx |
| Kesalahan server | {"message": ...} | 5xx |
Bentuk error message + errors bukan rekaan — itu persis bentuk bawaan Laravel untuk validation error yang sudah kita lihat di chapter sebelumnya, misalnya {"message": "The title field is required. (and 2 more errors)", "errors": {"title": ["The title field is required."]}} dengan status 422. Kita adopsi bentuk yang sama untuk error lainnya agar satu pola berlaku di seluruh API.
Karakter Unicode: Perilaku yang Perlu Diketahui #
JSON secara bawaan menyandikan karakter non-ASCII menjadi escape \uXXXX. Ini berlaku baik di json_encode() PHP murni maupun di response()->json() milik Laravel:
<?php
// routes/api.php
use Illuminate\Support\Facades\Route;
Route::get('/unicode', fn () => response()->json([
'judul' => 'Belajar REST API — Bagian 1',
'penulis' => 'Zoé',
]));Output yang Dihasilkan:
json_encode PHP : {"judul":"Belajar REST API \u2014 Bagian 1","penulis":"Zo\u00e9"}
response()->json : {"judul":"Belajar REST API \u2014 Bagian 1","penulis":"Zo\u00e9"}Baris pertama adalah json_encode() PHP murni, baris kedua body dari response()->json() — keduanya identik: tanda em-dash (—) menjadi \u2014 dan huruf é menjadi \u00e9. Ini valid — client mana pun yang mem-parse JSON akan mendapatkan teks aslinya kembali, dan teks Indonesia yang umumnya ASCII memang tidak terpengaruh. Namun bila kita ingin body terbaca sebagai UTF-8 mentah (misalnya untuk dipangkas ukurannya atau dibaca manusia), Laravel menyediakan setEncodingOptions():
<?php
// routes/api.php
use Illuminate\Support\Facades\Route;
Route::get('/unicode', fn () => response()->json([
'judul' => 'Belajar REST API — Bagian 1',
'penulis' => 'Zoé',
])->setEncodingOptions(JSON_UNESCAPED_UNICODE));Output yang Dihasilkan:
unicode tanpa escape : {"judul":"Belajar REST API — Bagian 1","penulis":"Zoé"}204 untuk Jawaban Tanpa Isi #
Endpoint DELETE adalah contoh terbaik untuk disiplin status: data sudah tiada, tidak ada yang perlu dilaporkan, jadi body memang sebaiknya kosong. Method response()->noContent() menghasilkan persis itu:
<?php
// routes/api.php
use App\Models\Post;
use Illuminate\Support\Facades\Route;
Route::delete('/posts/{post}', function (Post $post) {
$post->delete();
return response()->noContent();
});Output yang Dihasilkan (dari runner chapter Konsep REST API ):
DELETE /api/posts/1 → HTTP 204Status 204 dengan body kosong adalah kontrak yang jelas: “sukses, dan tidak ada body untuk dibaca”. Client tidak perlu mencoba mem-parse apa pun — berbeda dari 200 dengan body {} yang secara teknis juga sah, tetapi membuat client bertanya-tanya apakah ada data yang terlewat.
Disiplin response kini utuh: status jujur, envelope konsisten, dan perilaku Unicode dipahami. Bagian berikutnya menyusun sisi lain dari kontrak API — mengubah endpoint posts yang masih berupa closure menjadi controller CRUD penuh dengan API Resource Controllers
.