Membangun REST API dengan ResourceController, routing resource yang otomatis, format JSON response yang konsisten, API versioning, serta workflow testing API dengan Postman.

Sejauh ini aplikasi kita melayani halaman HTML untuk browser. Di episode 13 ini kita membuka pintu lain: REST API — antarmuka yang mengembalikan data terstruktur (JSON) sehingga bisa dikonsumsi aplikasi mobile, SPA, atau integrasi antar sistem.
Mengapa penting? Karena hampir semua produk modern memisahkan frontend dan backend, atau setidaknya menyediakan API publik. CodeIgniter 4 menyederhanakan ini dengan ResourceController dan resource routing — kalian mendapat endpoint CRUD standar hanya dengan beberapa baris.
Satu baris di app/Config/Routes.php menghasilkan 6 endpoint standar:
$routes->resource('posts');| Method | URL | Fungsi | Default controller method |
|---|---|---|---|
| GET | /posts | Daftar semua | index() |
| GET | /posts/(:num) | Detail satu | show($id) |
| POST | /posts | Buat baru | create() |
| PUT/PATCH | /posts/(:num) | Update | update($id) |
| DELETE | /posts/(:num) | Hapus | delete($id) |
| GET | /posts/new | Form buat (opsional) | new() |
| GET | /posts/(:num)/edit | Form edit (opsional) | edit($id) |
Jika kalian tidak butuh endpoint form (new/edit), nonaktifkan dengan 'only' => ['index','show','create','update','delete'].
Buat controller RESTful:
php spark make:controller Api/Posts --restful<?php
namespace App\Controllers\Api;
use App\Controllers\BaseController;
use App\Models\PostModel;
use CodeIgniter\RESTful\ResourceController;
class Posts extends ResourceController
{
protected $modelName = 'App\Models\PostModel';
protected $format = 'json';
public function index(): \CodeIgniter\HTTP\ResponseInterface
{
$posts = $this->model->where('status', 'published')
->orderBy('created_at', 'DESC')
->findAll();
return $this->respond($posts);
}
public function show($id = null): \CodeIgniter\HTTP\ResponseInterface
{
$post = $this->model->find($id);
if ($post === null) {
return $this->failNotFound('Post tidak ditemukan');
}
return $this->respond($post);
}
public function create(): \CodeIgniter\HTTP\ResponseInterface
{
$rules = [
'title' => 'required|max_length[255]',
'slug' => 'required|alpha_dash|is_unique[posts.slug]',
];
if (! $this->validate($rules)) {
return $this->failValidationErrors($this->validator->getErrors());
}
$id = $this->model->insert([
'title' => $this->request->getPost('title'),
'slug' => $this->request->getPost('slug'),
]);
return $this->respondCreated(['id' => $id]);
}
public function update($id = null): \CodeIgniter\HTTP\ResponseInterface
{
if ($this->model->find($id) === null) {
return $this->failNotFound('Post tidak ditemukan');
}
$this->model->update($id, [
'title' => $this->request->getPost('title') ?? $this->request->getRawInput()['title'] ?? '',
]);
return $this->respondUpdated(['id' => $id]);
}
public function delete($id = null): \CodeIgniter\HTTP\ResponseInterface
{
if ($this->model->find($id) === null) {
return $this->failNotFound('Post tidak ditemukan');
}
$this->model->delete($id);
return $this->respondDeleted(['id' => $id]);
}
}Property $format = 'json' membuat semua response otomatis di-encode JSON. Helper response bawaan ResourceController memberi format konsisten:
| Helper | Status code | Arti |
|---|---|---|
respond($data) | 200 | Sukses |
respondCreated($data) | 201 | Data berhasil dibuat |
respondUpdated($data) | 200 | Data diperbarui |
respondDeleted($data) | 200 | Data dihapus |
failNotFound($msg) | 404 | Tidak ditemukan |
failValidationErrors($errors) | 422 | Validasi gagal |
Tip
Untuk method PUT, CodeIgniter membaca body JSON mentah lewat $this->request->getRawInput()['field'] — bukan getPost() yang hanya membaca form-urlencoded. Pastikan kalian memakai cara yang benar sesuai format request client.
Consistency adalah aturan pertama API. Response kita selalu berbentuk:
{
"id": 12,
"title": "Belajar REST API",
"slug": "belajar-rest-api",
"status": "published",
"created_at": "2026-08-16 09:00:00"
}{
"error": "Post tidak ditemukan"
}Dokumentasikan struktur ini untuk client. Di produksi, pertimbangkan format envelope (data, meta, errors) agar client bisa mengecek status secara terprogram — standar yang kita pilih di sini cukup untuk series ini.
API akan berevolusi, dan perubahan yang memecah client tidak boleh terjadi diam-diam. Solusi klasiknya: versioning di URL (/api/v1/posts).
$routes->group('api/v1', ['namespace' => 'App\Controllers\Api\V1'], static function ($routes) {
$routes->resource('posts');
$routes->resource('categories');
});Namespace App\Controllers\Api\V1 menunjuk ke folder app/Controllers/Api/V1/. Saat API berubah, kalian buat app/Controllers/Api/V2/ dan route baru api/v2 — client lama tetap jalan di v1 sementara client baru bermigrasi ke v2.
Warning
Jangan menghapus versi lama secara mendadak. Umumkan deprecation, beri masa transisi, lalu hapus setelah client benar-benar pindah. Versioning di URL memang tidak se-elok header Accept, tapi jauh lebih mudah dipahami dan di-debug.
Postman mempermudah pengujian endpoint. Alur dasar:
php spark serve --port 8080
curl -i http://localhost:8080/api/v1/postsHTTP/1.1 200 OK
Content-Type: application/json
[{"id":1,"title":"Hello World","slug":"hello-world","status":"published","created_at":"2026-08-16 09:00:00"}]Di Postman:
GET http://localhost:8080/api/v1/posts.POST, pilih body raw → JSON, kirim {"title":"Post Baru","slug":"post-baru"}.Note
Di episode 8 filter CSRF kita aktifkan global. Untuk API endpoint, CSRF dikecualikan (ganti dengan API key/JWT — episode 19). Jika kalian mendapat error CSRF di /api/*, tambahkan except pada filter csrf untuk prefix api/.
$routes->resource('posts') menghasilkan 7 endpoint standar CRUD.ResourceController + $format = 'json' + helper respond/fail = response konsisten.getRawInput(), bukan getPost().api/v1/api/v2 + namespace per versi.Inti yang harus dibawa pulang:
respond*/fail* agar format error konsisten.getRawInput() untuk body JSON; versioning URL untuk evolusi API.Di episode 14 selanjutnya kita akan membahas caching & performance — cache drivers File/Redis/Memcached, page cache dan query caching, serta praktik cache list API dengan invalidation yang benar. Aplikasi kalian mulai berurusan dengan traffic!