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

  1. Install XAMPP:
    • Download from XAMPP Official Website.
    • Follow the installation wizard and install it in the default directory.
  2. Start Apache and MySQL:
    • Open the XAMPP Control Panel.
    • Start the Apache and MySQL modules.

2. Creating the Database

  1. Access phpMyAdmin:
    • Navigate to http://localhost/phpmyadmin/ in your browser.
  2. 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”.
  3. Create a products Table:
    • Select the api_demo database.
    • Click on “New” to create a table.
    • Define the table with the following fields:
    Field Type Null Key Default Extra id INT NO PRI NULL AUTO_INCREMENT name VARCHAR(100) NO NULL description TEXT YES NULL price DECIMAL(10,2) NO NULL created_at TIMESTAMP NO CURRENT_TIMESTAMP
    • Click “Save”.

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.

  1. Initialize Composer:
    • Navigate to your project directory in the terminal.
    • Run: composer init
    • Follow the prompts to set up composer.json.
  2. 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.
Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *