Project Overview
A RESTful API (Representational State Transfer Application Programming Interface) allows different applications to communicate with each other over the web. Building a RESTful API with PHP enables your followers to create endpoints that can handle various HTTP requests (GET, POST, PUT, DELETE) for data manipulation, making it essential for modern web and mobile applications.
Prerequisites
Before starting, ensure you have the following:
- Web Server: Apache (using XAMPP, WAMP, or MAMP)
- PHP: Version 7.4 or higher
- MySQL: For database management
- Code Editor: VS Code, Sublime Text, PHPStorm, etc.
- Composer: Dependency management (optional but recommended)
- Basic Understanding of HTTP Methods and REST Principles
Step-by-Step Procedure
1. Setting Up the Development Environment
- Install XAMPP:
- Download from XAMPP Official Website.
- Follow the installation wizard and install it in the default directory.
- Start Apache and MySQL:
- Open the XAMPP Control Panel.
- Start the Apache and MySQL modules.
2. Creating the Database
- Access phpMyAdmin:
- Navigate to
http://localhost/phpmyadmin/
in your browser.
- Navigate to
- Create a New Database:
- Click on “New” in the left sidebar.
- Name the database
api_demo
. - Choose “utf8mb4_unicode_ci” as the collation.
- Click “Create”.
- Create a
products
Table:- Select the
api_demo
database. - Click on “New” to create a table.
- Define the table with the following fields:
- Click “Save”.
- Select the
3. Project Structure
Organize your project files as follows:
restful-api/
├── config/
│ └── db.php
├── api/
│ ├── index.php
│ ├── products.php
├── .htaccess
├── composer.json
└── README.md
4. Configuration
a. Database Connection (config/db.php
)
Create a file named db.php
inside the config
directory to handle database connections.
<?php
// config/db.php
$host = 'localhost';
$db = 'api_demo';
$user = 'root'; // Default XAMPP MySQL user
$pass = ''; // Default XAMPP MySQL password is empty
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Enable exceptions
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // Fetch associative arrays
PDO::ATTR_EMULATE_PREPARES => false, // Disable emulation
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
// Handle connection errors
http_response_code(500);
echo json_encode(['error' => 'Database connection failed']);
exit;
}
?>
b. Setting Up Autoloading with Composer (Optional)
Using Composer for autoloading and managing dependencies can streamline your project.
- Initialize Composer:
- Navigate to your project directory in the terminal.
- Run:
composer init
- Follow the prompts to set up
composer.json
.
- Install Dependencies (Optional):
- For this simple API, external dependencies are not required. However, libraries like Slim Framework or Lumen can be integrated for more complex APIs.
5. Creating the API Endpoint
a. Main API Entry Point (api/index.php
)
This file will handle routing based on the requested URL and HTTP method.
<?php
// api/index.php
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Origin: *"); // Allow all origins (modify as needed)
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
require '../config/db.php';
// Get the HTTP method, path and input of the request
$method = $_SERVER['REQUEST_METHOD'];
$request = explode('/', trim($_SERVER['PATH_INFO'],'/'));
$input = json_decode(file_get_contents('php://input'),true);
// Routing
if ($request[0] !== 'products') {
http_response_code(404);
echo json_encode(['error' => 'Endpoint not found']);
exit;
}
switch ($method) {
case 'GET':
if (isset($request[1])) {
// Get single product
$id = (int)$request[1];
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = ?');
$stmt->execute([$id]);
$product = $stmt->fetch();
if ($product) {
echo json_encode($product);
} else {
http_response_code(404);
echo json_encode(['error' => 'Product not found']);
}
} else {
// Get all products
$stmt = $pdo->query('SELECT * FROM products');
$products = $stmt->fetchAll();
echo json_encode($products);
}
break;
case 'POST':
// Create new product
if (!isset($input['name']) || !isset($input['price'])) {
http_response_code(400);
echo json_encode(['error' => 'Name and price are required']);
exit;
}
$name = $input['name'];
$description = $input['description'] ?? '';
$price = $input['price'];
$stmt = $pdo->prepare('INSERT INTO products (name, description, price) VALUES (?, ?, ?)');
if ($stmt->execute([$name, $description, $price])) {
http_response_code(201);
echo json_encode(['message' => 'Product created', 'id' => $pdo->lastInsertId()]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Failed to create product']);
}
break;
case 'PUT':
// Update existing product
if (!isset($request[1])) {
http_response_code(400);
echo json_encode(['error' => 'Product ID is required']);
exit;
}
$id = (int)$request[1];
// Check if product exists
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = ?');
$stmt->execute([$id]);
$product = $stmt->fetch();
if (!$product) {
http_response_code(404);
echo json_encode(['error' => 'Product not found']);
exit;
}
// Update fields
$name = $input['name'] ?? $product['name'];
$description = $input['description'] ?? $product['description'];
$price = $input['price'] ?? $product['price'];
$stmt = $pdo->prepare('UPDATE products SET name = ?, description = ?, price = ? WHERE id = ?');
if ($stmt->execute([$name, $description, $price, $id])) {
echo json_encode(['message' => 'Product updated']);
} else {
http_response_code(500);
echo json_encode(['error' => 'Failed to update product']);
}
break;
case 'DELETE':
// Delete product
if (!isset($request[1])) {
http_response_code(400);
echo json_encode(['error' => 'Product ID is required']);
exit;
}
$id = (int)$request[1];
// Check if product exists
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = ?');
$stmt->execute([$id]);
$product = $stmt->fetch();
if (!$product) {
http_response_code(404);
echo json_encode(['error' => 'Product not found']);
exit;
}
// Delete product
$stmt = $pdo->prepare('DELETE FROM products WHERE id = ?');
if ($stmt->execute([$id])) {
echo json_encode(['message' => 'Product deleted']);
} else {
http_response_code(500);
echo json_encode(['error' => 'Failed to delete product']);
}
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
break;
}
?>
b. Products API Handler (api/products.php
)
Alternatively, for better organization, you can separate the routing logic into different files. However, for simplicity, the above single index.php
handles all product-related API requests.
c. Configuring URL Rewriting with .htaccess
To ensure that API requests are correctly routed to api/index.php
, set up URL rewriting.
Create a .htaccess
file in the root of your project (restful-api/.htaccess
) with the following content:
RewriteEngine On
RewriteRule ^api/(.*)$ api/index.php?/$1 [QSA,NC,L]
Note: Ensure that mod_rewrite
is enabled in your Apache configuration. In XAMPP, you can enable it by uncommenting the following line in apache/conf/httpd.conf
:
LoadModule rewrite_module modules/mod_rewrite.so
Then restart Apache.
6. Testing the API
You can test the API endpoints using tools like Postman or cURL.
a. Getting All Products
- Endpoint:
GET http://localhost/restful-api/api/products
- Response: JSON array of all products.
b. Getting a Single Product
- Endpoint:
GET http://localhost/restful-api/api/products/{id}
- Response: JSON object of the specified product.
c. Creating a New Product
- Endpoint:
POST http://localhost/restful-api/api/products
- Headers:
Content-Type: application/json
- Body:
{ "name": "Sample Product", "description": "This is a sample product.", "price": 29.99 }
- Response: Confirmation message with the new product ID.
d. Updating a Product
- Endpoint:
PUT http://localhost/restful-api/api/products/{id}
- Headers:
Content-Type: application/json
- Body:
{ "name": "Updated Product Name", "price": 39.99 }
- Response: Confirmation message.
e. Deleting a Product
- Endpoint:
DELETE http://localhost/restful-api/api/products/{id}
- Response: Confirmation message.
7. Deployment Considerations
When deploying your RESTful API to a live server, consider the following:
- Secure Endpoints: Implement authentication (e.g., API keys, OAuth) to protect sensitive endpoints.
- Input Validation: Ensure all inputs are thoroughly validated and sanitized to prevent SQL injection and other attacks.
- Rate Limiting: Prevent abuse by limiting the number of requests from a single IP or API key.
- HTTPS: Use HTTPS to encrypt data transmission.
- CORS Configuration: Restrict
Access-Control-Allow-Origin
to trusted domains to enhance security. - Error Handling: Provide meaningful error messages without exposing sensitive information.
- Logging: Implement logging for monitoring API usage and troubleshooting issues.
8. Enhancements and Best Practices
- Use a Framework: For more complex APIs, consider using PHP frameworks like Slim, Lumen, or Laravel, which offer robust routing, middleware, and other features.
- Versioning: Implement API versioning (e.g.,
/api/v1/products
) to manage updates without breaking existing clients. - Documentation: Provide comprehensive API documentation using tools like Swagger or Apiary.
- Testing: Write automated tests to ensure API reliability and prevent regressions.