✦ Selamat Idul Fitri 1447 H πŸŒ™ Taqabbalallahu minna wa minkum. Mohon maaf lahir dan batin. ✦
ALFAkwt

Membuat REST API Sederhana dengan PHP Murni: Cara Saya Tanpa Framework

Β· 0 komentar Β· Β± 5 menit baca Β· πŸ‘ 100 dilihat

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.


Komentar

Belum ada komentar. Jadilah yang pertama menulis.

Tulis Komentar

↑