
PHP REST API Development: Building a Practical Example with Best Practices
Architecture and Requirements
The API will support the following operations:
GET /tasks: Retrieve all tasks.GET /tasks/{id}: Retrieve a specific task.POST /tasks: Create a new task.PUT /tasks/{id}: Update an existing task.DELETE /tasks/{id}: Delete a task.
We'll use PHP's native $_SERVER and $_GET/$_POST superglobals to simulate routing and request handling, without relying on external frameworks. For data storage, we'll use an in-memory array to represent a database.
Implementation
Step 1: Define the Task Resource
<?php
$tasks = [
1 => ['id' => 1, 'title' => 'Design UI', 'completed' => false],
2 => ['id' => 2, 'title' => 'Write documentation', 'completed' => true],
];Step 2: Determine the HTTP Method and Route
$method = $_SERVER['REQUEST_METHOD'];
$request = explode('/', trim($_SERVER['PATH_INFO'], '/'));
switch ($request[0]) {
case 'tasks':
handleTasks($method, $request[1] ?? null);
break;
default:
http_response_code(404);
echo json_encode(['error' => 'Not found']);
break;
}
function handleTasks($method, $id = null) {
global $tasks;
switch ($method) {
case 'GET':
if ($id) {
if (isset($tasks[$id])) {
echo json_encode($tasks[$id]);
} else {
http_response_code(404);
echo json_encode(['error' => 'Task not found']);
}
} else {
echo json_encode(array_values($tasks));
}
break;
case 'POST':
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['title'])) {
http_response_code(400);
echo json_encode(['error' => 'Title is required']);
return;
}
$id = count($tasks) + 1;
$tasks[$id] = [
'id' => $id,
'title' => $data['title'],
'completed' => $data['completed'] ?? false
];
http_response_code(201);
echo json_encode($tasks[$id]);
break;
case 'PUT':
if (!$id || !isset($tasks[$id])) {
http_response_code(404);
echo json_encode(['error' => 'Task not found']);
return;
}
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['title'])) {
http_response_code(400);
echo json_encode(['error' => 'Title is required']);
return;
}
$tasks[$id]['title'] = $data['title'];
$tasks[$id]['completed'] = $data['completed'] ?? $tasks[$id]['completed'];
echo json_encode($tasks[$id]);
break;
case 'DELETE':
if (!$id || !isset($tasks[$id])) {
http_response_code(404);
echo json_encode(['error' => 'Task not found']);
return;
}
unset($tasks[$id]);
echo json_encode(['message' => 'Task deleted']);
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
break;
}
}Best Practices in REST API Development
| Practice | Description |
|---|---|
| Use proper HTTP status codes | Return 200, 201, 400, 404, 405, etc., to indicate success or errors clearly. |
| Validate input data | Ensure required fields are present and sanitize inputs to avoid malicious data. |
| Use consistent JSON structure | Always return JSON with predictable keys like id, title, and completed. |
| Support pagination and filtering | Add ?page=2 or ?status=completed for large datasets. |
| Use middleware for authentication | Protect endpoints using tokens or session-based authentication. |
| Document your API | Provide usage examples and endpoint descriptions for other developers. |
Comparison of REST API Methods
| HTTP Method | Description | Use Case |
|---|---|---|
GET | Retrieve data | Fetching a resource or list |
POST | Create data | Adding a new resource |
PUT | Update data | Modifying an existing resource |
DELETE | Remove data | Deleting a resource |
PATCH | Partial update | Updating one or more fields |
Note: This example does not use PATCH, but it can be implemented similarly to PUT with selective updates.
Error Handling and Response Structure
All API responses should include a consistent structure. A successful response might look like this:
{
"id": 3,
"title": "Test task",
"completed": false
}An error response should be:
{
"error": "Title is required"
}or
{
"error": "Method not allowed"
}