Setiap kali saya bilang bikin API pakai PHP murni tanpa framework, reaksi yang sering muncul adalah: "Kenapa tidak pakai Laravel aja? Kan ada Sanctum, ada resource collection, semua sudah ada."
Jawabannya sederhana: untuk kebutuhan tertentu, membawa Laravel ke dalam proyek itu seperti datang ke warung nasi dengan membawa seluruh dapur restoran. Overkill.
Artikel ini tentang bagaimana saya bikin REST API sederhana dengan PHP murni β untuk backend aplikasi Flutter yang saya kerjakan sendiri.
Struktur yang Saya Pakai
Tidak perlu struktur yang rumit untuk API sederhana. Ini yang saya gunakan:
api/
βββ index.php # Entry point, routing utama
βββ config/
β βββ database.php # Koneksi database
βββ helpers/
β βββ response.php # Helper untuk format respons JSON
β βββ auth.php # Validasi token
βββ endpoints/
βββ articles.php # Handler untuk /articles
βββ categories.php # Handler untuk /categories
Semua request masuk ke index.php, yang membaca URL dan method HTTP, lalu mendelegasikan ke file endpoint yang sesuai.
Entry Point dan Routing Sederhana
<?php
// index.php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
// Handle preflight request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
require_once 'config/database.php';
require_once 'helpers/response.php';
require_once 'helpers/auth.php';
// Parse URL path
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = str_replace('/api', '', $path);
$segments = explode('/', trim($path, '/'));
$endpoint = $segments[0] ?? '';
$id = $segments[1] ?? null;
$method = $_SERVER['REQUEST_METHOD'];
// Routing
switch ($endpoint) {
case 'articles':
require_once 'endpoints/articles.php';
handleArticles($method, $id);
break;
case 'categories':
require_once 'endpoints/categories.php';
handleCategories($method, $id);
break;
default:
respond(404, ['error' => 'Endpoint tidak ditemukan']);
}
Helper Respons: Konsistensi Format
<?php
// helpers/response.php
function respond(int $statusCode, array $data): void {
http_response_code($statusCode);
echo json_encode([
'success' => $statusCode >= 200 && $statusCode < 300,
'data' => $statusCode >= 200 && $statusCode < 300 ? $data : null,
'error' => $statusCode >= 400 ? $data['error'] ?? 'Terjadi kesalahan' : null,
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}
function getRequestBody(): array {
$raw = file_get_contents('php://input');
return json_decode($raw, true) ?? [];
}
Dengan helper ini, semua endpoint menggunakan format respons yang konsisten. Flutter client tidak perlu menangani berbagai format berbeda dari tiap endpoint.
Endpoint Articles: CRUD Lengkap
<?php
// endpoints/articles.php
function handleArticles(string $method, ?string $id): void {
global $pdo;
switch ($method) {
case 'GET':
if ($id) {
// GET /articles/123 β ambil satu artikel
$stmt = $pdo->prepare(
'SELECT p.id, p.title, p.slug, p.content, p.created_at,
c.name as category_name
FROM posts p
LEFT JOIN categories c ON p.category_id = c.id
WHERE p.id = ?'
);
$stmt->execute([$id]);
$article = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$article) {
respond(404, ['error' => 'Artikel tidak ditemukan']);
}
respond(200, $article);
} else {
// GET /articles β ambil semua
$page = (int)($_GET['page'] ?? 1);
$limit = (int)($_GET['limit'] ?? 10);
$offset = ($page - 1) * $limit;
$stmt = $pdo->prepare(
'SELECT p.id, p.title, p.slug, p.created_at, c.name as category_name
FROM posts p
LEFT JOIN categories c ON p.category_id = c.id
ORDER BY p.created_at DESC
LIMIT ? OFFSET ?'
);
$stmt->execute([$limit, $offset]);
$articles = $stmt->fetchAll(PDO::FETCH_ASSOC);
$total = $pdo->query('SELECT COUNT(*) FROM posts')->fetchColumn();
respond(200, [
'articles' => $articles,
'total' => (int)$total,
'page' => $page,
'total_pages' => (int)ceil($total / $limit),
]);
}
break;
case 'POST':
requireAuth(); // validasi token
$body = getRequestBody();
if (empty($body['title']) || empty($body['content'])) {
respond(400, ['error' => 'Title dan content wajib diisi']);
}
$slug = generateSlug($body['title']);
$stmt = $pdo->prepare(
'INSERT INTO posts (title, slug, content, category_id, created_at)
VALUES (?, ?, ?, ?, NOW())'
);
$stmt->execute([
$body['title'],
$slug,
$body['content'],
$body['category_id'] ?? null,
]);
respond(201, ['id' => (int)$pdo->lastInsertId(), 'slug' => $slug]);
break;
default:
respond(405, ['error' => 'Method tidak diizinkan']);
}
}
function generateSlug(string $title): string {
$slug = strtolower($title);
$slug = preg_replace('/[^a-z0-9\s-]/', '', $slug);
$slug = preg_replace('/[\s-]+/', '-', $slug);
return trim($slug, '-');
}
Autentikasi dengan Token Sederhana
Untuk API yang hanya dipakai oleh aplikasi saya sendiri, saya tidak butuh sistem autentikasi yang kompleks. Token sederhana yang disimpan di database sudah cukup:
<?php
// helpers/auth.php
function requireAuth(): void {
global $pdo;
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';
if (!preg_match('/^Bearer (.+)$/', $authHeader, $matches)) {
respond(401, ['error' => 'Token tidak ditemukan']);
}
$token = $matches[1];
$stmt = $pdo->prepare('SELECT id FROM api_tokens WHERE token = ? AND active = 1');
$stmt->execute([$token]);
if (!$stmt->fetch()) {
respond(401, ['error' => 'Token tidak valid']);
}
}
Kenapa Saya Pilih Ini daripada Laravel
Untuk API yang endpoint-nya tidak lebih dari sepuluh dan hanya diakses oleh satu aplikasi Flutter, solusi ini lebih dari cukup. Tidak ada autoload, tidak ada service container, tidak ada middleware chain β semuanya eksplisit dan bisa saya trace dari atas ke bawah.
Deployment-nya juga jauh lebih sederhana: upload beberapa file PHP ke hosting shared, selesai. Tidak ada proses build, tidak ada setup environment yang rumit.
Kalau proyek berkembang dan butuh fitur yang lebih kompleks β autentikasi multi-level, rate limiting yang canggih, dokumentasi API otomatis β mungkin saat itu saya akan pertimbangkan Laravel. Tapi selama kebutuhannya masih sederhana, saya tidak mau menambahkan kompleksitas yang belum perlu.
Pernah bikin API dari nol tanpa framework? Atau Anda tim "selalu pakai Laravel"? Saya penasaran dengan argumennya β diskusi di komentar selalu lebih menarik dari yang saya tulis di artikel.
Belum ada komentar. Jadilah yang pertama menulis.
Tulis Komentar