REST API adalah pintu data yang dipakai aplikasi mobile, frontend JavaScript, atau sistem lain. Laravel menyediakan semua yang dibutuhkan untuk membuatnya dengan rapi. Di tutorial ini kita membuat API untuk resource Produk dengan operasi lengkap.
Belum paham konsep REST? Baca dulu Belajar REST API dari Dasar.
1. Persiapan model dan tabel
php artisan make:model Produk -m
Migration:
Schema::create('produks', function (Blueprint $table) {
$table->id();
$table->string('nama');
$table->decimal('harga', 12, 2);
$table->unsignedInteger('stok')->default(0);
$table->timestamps();
});
Model:
class Produk extends Model
{
protected $fillable = ['nama', 'harga', 'stok'];
}
Jalankan php artisan migrate.
2. Mendefinisikan route API
Route API ditulis di routes/api.php dan otomatis diberi awalan /api serta dijaga middleware api (tanpa session/CSRF dan dengan pembatasan laju).
use App\Http\Controllers\Api\ProdukController;
Route::apiResource('produk', ProdukController::class);
apiResource membuat lima route tanpa create/edit (karena API tidak punya form HTML):
| Method | URL | Aksi |
|---|---|---|
| GET | /api/produk | index: daftar |
| POST | /api/produk | store: buat |
| GET | /api/produk/{id} | show: detail |
| PUT/PATCH | /api/produk/{id} | update: ubah |
| DELETE | /api/produk/{id} | destroy: hapus |
3. API Resource: bentuk respons yang konsisten
Jangan mengembalikan model mentah karena kamu bisa tidak sengaja membocorkan kolom internal. Gunakan API Resource untuk mengontrol bentuk JSON:
php artisan make:resource ProdukResource
class ProdukResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'nama' => $this->nama,
'harga' => (float) $this->harga,
'stok' => $this->stok,
'dibuat'=> $this->created_at->toIso8601String(),
];
}
}
4. Controller
php artisan make:controller Api/ProdukController --api --model=Produk
class ProdukController extends Controller
{
public function index()
{
return ProdukResource::collection(
Produk::latest()->paginate(15)
);
}
public function store(Request $request)
{
$data = $request->validate([
'nama' => 'required|string|max:255',
'harga' => 'required|numeric|min:0',
'stok' => 'integer|min:0',
]);
$produk = Produk::create($data);
return (new ProdukResource($produk))
->response()
->setStatusCode(201);
}
public function show(Produk $produk)
{
return new ProdukResource($produk);
}
public function update(Request $request, Produk $produk)
{
$data = $request->validate([
'nama' => 'sometimes|required|string|max:255',
'harga' => 'sometimes|required|numeric|min:0',
'stok' => 'sometimes|integer|min:0',
]);
$produk->update($data);
return new ProdukResource($produk);
}
public function destroy(Produk $produk)
{
$produk->delete();
return response()->noContent(); // 204
}
}
Detail penting:
paginate(15)otomatis menambahkanlinksdanmeta(halaman, total) pada respons.- Validasi yang gagal pada permintaan JSON otomatis menghasilkan respons 422 berisi daftar
errors, tanpa kode tambahan, asalkan klien mengirim headerAccept: application/json. - Route model binding mengembalikan 404 otomatis jika ID tidak ditemukan.
sometimesberarti "validasi hanya jika field dikirim", cocok untuk update parsial.
5. Kode status yang benar
| Situasi | Kode |
|---|---|
| Berhasil membaca / mengubah | 200 OK |
| Berhasil membuat | 201 Created |
| Berhasil menghapus (tanpa isi) | 204 No Content |
| Input tidak valid | 422 Unprocessable Entity |
| Tidak login | 401 Unauthorized |
| Login tetapi tidak berhak | 403 Forbidden |
| Tidak ditemukan | 404 Not Found |
| Terlalu banyak permintaan | 429 Too Many Requests |
6. Menguji dengan curl
# Buat produk
curl -X POST http://localhost:8000/api/produk \
-H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"nama":"Pensil","harga":2500,"stok":100}'
# Daftar
curl -H "Accept: application/json" http://localhost:8000/api/produk
# Ubah sebagian
curl -X PATCH http://localhost:8000/api/produk/1 \
-H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"stok":80}'
# Hapus
curl -X DELETE -H "Accept: application/json" http://localhost:8000/api/produk/1
Alat grafis seperti Postman, Insomnia, atau Bruno juga nyaman dipakai.
7. Mengamankan dengan Sanctum
Tanpa autentikasi, siapa pun bisa menghapus datamu. Sanctum menyediakan token sederhana untuk API:
Route::apiResource('produk', ProdukController::class)
->middleware('auth:sanctum');
Setelah login, buat token: $user->createToken('mobile')->plainTextToken. Klien mengirimnya di header Authorization: Bearer <token>. Selalu gunakan HTTPS agar token tidak bisa disadap.
8. Kesalahan umum
- Lupa header
Accept: application/json, sehingga error tampil sebagai redirect/HTML. - Mengembalikan 200 untuk semua hal termasuk error. Gunakan kode status yang tepat.
- Tidak memakai
$fillable, sehingga error mass assignment atau lebih buruk, kolom sensitif bisa diubah klien. - Tidak membatasi laju (
throttle) pada endpoint login. - Mengirim data sensitif (password hash, token) dalam respons.
Menangani relasi dan filter pada API
Tambahkan relasi pada resource dengan whenLoaded agar tidak memicu query tambahan yang tidak perlu:
'kategori' => new KategoriResource($this->whenLoaded('kategori')),
Klien dapat meminta relasi lewat query string yang kamu whitelist:
$produk = Produk::query()
->when($request->boolean('dengan_kategori'), fn ($q) => $q->with('kategori'))
->when($request->kategori_id, fn ($q, $id) => $q->where('kategori_id', $id))
->paginate(15);
Format respons list yang dihasilkan Laravel
Pada ProdukResource::collection($paginator), bentuk JSON-nya:
{
"data": [ { "id": 1, "nama": "Pensil", "harga": 2500, "stok": 100 } ],
"links": { "first": "...?page=1", "next": "...?page=2", "prev": null },
"meta": { "current_page": 1, "per_page": 15, "total": 42, "last_page": 3 }
}
Klien tinggal membaca meta.total dan links.next untuk membuat navigasi halaman.
Menulis test untuk API
public function test_membuat_produk_gagal_jika_harga_negatif(): void
{
$this->actingAs(User::factory()->create(), 'sanctum')
->postJson('/api/produk', ['nama' => 'X', 'harga' => -5])
->assertStatus(422)
->assertJsonValidationErrors(['harga']);
}
public function test_menghapus_mengembalikan_204(): void
{
$produk = Produk::factory()->create();
$this->actingAs(User::factory()->create(), 'sanctum')
->deleteJson("/api/produk/{$produk->id}")
->assertNoContent();
$this->assertDatabaseMissing('produks', ['id' => $produk->id]);
}
Jalankan dengan php artisan test. Test otomatis memberi keberanian untuk mengubah kode tanpa takut merusak perilaku yang sudah benar.
CORS: saat frontend berbeda domain
Jika frontend (misalnya app.contoh.com) memanggil API di api.contoh.com, browser memblokir permintaan kecuali server mengizinkan lewat CORS. Atur config/cors.php:
'paths' => ['api/*'],
'allowed_origins' => ['https://app.contoh.com'],
'allowed_methods' => ['*'],
'allowed_headers' => ['*'],
Jangan mengisi allowed_origins dengan * pada API yang memakai kredensial. Sebutkan domain yang dipercaya saja.
Daftar tugas sebelum API dipakai klien
- [ ] Semua endpoint memakai kode status yang benar.
- [ ] Validasi dan pesan error konsisten.
- [ ] Autentikasi dan pembatasan laju terpasang.
- [ ] Paginasi pada semua daftar.
- [ ] Tidak ada data sensitif di respons.
- [ ] Dokumentasi contoh request/response tersedia.
- [ ] Test untuk jalur sukses dan gagal.
Kesimpulan
Pola REST API di Laravel: route apiResource → validasi → API Resource → kode status yang tepat → autentikasi. Setelah paham pola ini, menambah resource baru hanya soal mengulang langkah yang sama. Untuk praktik terbaik yang lebih lanjut seperti versioning dan format error, lihat Tips Membuat API dengan Laravel.
Komentar (0)
Masuk / Daftar