Membangun REST API dengan PHP modern: routing, controller, validation, dan response pattern, lalu memilih framework yang tepat dengan perbandingan Laravel full-stack, Symfony berbasis komponen, Slim micro, dan CodeIgniter yang ringan untuk kebutuhan produksi nyata.

Di episode 12 kita menangani request/response secara manual. Di episode ini kita melakukan lompatan yang sama seperti engineer produksi: memakai framework untuk membangun REST API. Framework menyediakan struktur, routing, validasi, autentikasi, dan ribuan solusi yang sudah teruji — sehingga kalian fokus pada logika bisnis, bukan mengulang boilerplate.
Mengapa penting? Di dunia kerja nyata, hampir tidak ada yang menulis PHP tanpa framework. Memahami pola framework (routing → controller → validation → response) juga membuat kalian mudah pindah antar framework, karena konsepnya sama. Episode ini sekaligus menjawab pertanyaan klasik: Laravel, Symfony, Slim, atau CodeIgniter?
REST API mengekspos resource melalui URL + HTTP method:
| Method | URL | Aksi |
|---|---|---|
| GET | /api/users | Daftar user |
| POST | /api/users | Buat user baru |
| GET | /api/users/{id} | Detail user |
| PUT/PATCH | /api/users/{id} | Perbarui user |
| DELETE | /api/users/{id} | Hapus user |
Prinsip REST yang perlu dipegang: resource berbentuk kata benda (bukan /getUsers), stateless (setiap request membawa semua konteks), dan status code yang jujur (200/201/400/401/404/422/500).
Slim adalah pilihan tepat untuk memahami pola framework tanpa abstraksi berlebihan:
composer require slim/slim slim/psr7<?php
declare(strict_types=1);
use Slim\Factory\AppFactory;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
require __DIR__ . "/../vendor/autoload.php";
$app = AppFactory::create();
$app->get("/api/users", function (Request $req, Response $res): Response {
$users = [
["id" => 1, "nama" => "Devvnull"],
["id" => 2, "nama" => "Anon"],
];
$res->getBody()->write(json_encode($users));
return $res->withHeader("Content-Type", "application/json");
});
$app->get("/api/users/{id}", function (Request $req, Response $res, array $args): Response {
$id = (int) $args["id"];
$payload = ["id" => $id, "nama" => "User " . $id];
$res->getBody()->write(json_encode($payload));
return $res->withHeader("Content-Type", "application/json");
});
$app->run();Jalankan dengan built-in server atau php -S localhost:8080 -t public. Pola yang terlihat: routing mendefinisikan URL, closure/controller mengembalikan response PSR-7.
Saat logika membesar, pindahkan ke controller class (bukan closure di file route):
namespace App\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
final class UserController
{
public function __construct(private UserRepository $repo) {}
public function index(ServerRequestInterface $req, ResponseInterface $res): ResponseInterface
{
$res->getBody()->write(json_encode($this->repo->all()));
return $res->withHeader("Content-Type", "application/json");
}
public function show(ServerRequestInterface $req, ResponseInterface $res, array $args): ResponseInterface
{
$user = $this->repo->find((int) $args["id"]);
$res->getBody()->write(json_encode($user));
return $res->withHeader("Content-Type", "application/json");
}
}$app->get("/api/users", [UserController::class, "index"]);
$app->get("/api/users/{id}", [UserController::class, "show"]);Controller tipis + service layer yang berisi logika = struktur yang bisa di-test (episode 10) dan dipelihara.
Validasi input di controller, balas 422 dengan daftar error:
$app->post("/api/users", function (Request $req, Response $res): Response {
$body = json_decode((string) $req->getBody(), true);
$errors = [];
$nama = trim($body["nama"] ?? "");
$email = strtolower(trim($body["email"] ?? ""));
if ($nama === "" || strlen($nama) > 100) {
$errors["nama"] = "Nama wajib 1-100 karakter.";
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors["email"] = "Email tidak valid.";
}
if ($errors !== []) {
$res->getBody()->write(json_encode(["errors" => $errors]));
return $res->withStatus(422)
->withHeader("Content-Type", "application/json");
}
$res->getBody()->write(json_encode(["id" => random_int(1, 999)]));
return $res->withStatus(201)
->withHeader("Content-Type", "application/json");
});Di Laravel, validasi ini menjadi $request->validate([...]) yang lebih ringkas — kita lihat di bawah.
Laravel menyediakan pola yang lebih produktif. Install:
composer create-project laravel/laravel toko-api
cd toko-api
php artisan serveRoute + controller + Eloquent + validasi:
use App\Http\Controllers\Api\UserController;
Route::apiResource("users", UserController::class);namespace App\Http\Controllers\Api;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
public function index()
{
return User::query()->orderBy("nama")->get();
}
public function store(Request $request)
{
$data = $request->validate([
"nama" => ["required", "string", "max:100"],
"email" => ["required", "email", "unique:users"],
]);
return response()->json(User::create($data), 201);
}
}Laravel bahkan menyediakan API Resource untuk membentuk response secara konsisten:
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
"id" => $this->id,
"nama" => $this->nama,
"email" => $this->email,
];
}
}| Framework | Tipe | Kelebihan | Kapan dipilih |
|---|---|---|---|
| Laravel | Full-stack | Produktif, ekosistem luas, ORM bawaan | Aplikasi web/API lengkap |
| Symfony | Berbasis komponen | Fleksibel, reusable, standar PSR ketat | Aplikasi enterprise, API besar |
| Slim | Micro | Ringan, minim abstraksi | API kecil, pembelajaran, prototyping |
| CodeIgniter | Ringan | Simpel, rendah overhead | Aplikasi kecil di hosting sederhana |
Tip
Untuk belajar: Slim memperjelas konsep tanpa abstraksi. Untuk produksi di 2026: Laravel adalah pilihan default yang paling cepat membangun produk, dengan Symfony untuk kebutuhan enterprise yang lebih kompleks. Kalian tidak salah memilih keduanya — pilih sesuai kompleksitas proyek.
/getUser) — melanggar REST; gunakan kata benda + method HTTP.json_encode tanpa cek error — di Slim pastikan body terisi sebelum return; kegagalan encode bisa menghasilkan body kosong.Inti yang harus dibawa pulang:
apiResource, validasi $request->validate(), Eloquent, dan API Resource.Di episode 14 selanjutnya kita mengamankan API tersebut: Authentication & Authorization — hash password dengan bcrypt/argon2, session-based auth untuk web, JWT untuk API, lalu roles & permissions dengan Spatie Permission atau policy, serta middleware sebagai penjaga gerbang. Ini topik yang menentukan kepercayaan pengguna terhadap aplikasi kalian.