PHP REST API

How to Build a REST API with PHP and MySQL (With CORS & .htaccess) – Step-by-Step Guide

Build PHP MySQL REST API with .htaccess & CORS

In this tutorial, we will build a simple yet complete REST API in PHP using MySQL. We’ll implement CRUD operations (Create, Read, Update, Delete), handle file uploads, and support clean URLs using .htaccess. The tutorial is compatible with Angular or any frontend framework.

 Database Setup

Create a MySQL database and the users table using the following SQL:

CREATE DATABASE user_api;
USE user_api;
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  first_name VARCHAR(100),
  last_name VARCHAR(100),
  email VARCHAR(150),
  password VARCHAR(255),
  image VARCHAR(255),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

 Folder Structure

Project structure:

php-rest-api/
├── uploads/
├── user/
│   ├── create.php
│   ├── read.php
│   ├── read_single.php
│   ├── update.php
│   └── delete.php
├── .htaccess
├── db.php
└── index.php

index.php – Main API Entry Point

This file enables CORS, parses incoming requests, and routes them to the appropriate handler (create, read, update, delete).

<?php
// Enable CORS headers
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Content-Type: application/json");

// Handle preflight request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    exit(0);
}

// Include DB connection
require_once 'db.php';

// Parse the requested URL
$method = $_SERVER['REQUEST_METHOD'];
$url = isset($_GET['url']) ? $_GET['url'] : '';

// Basic routing
switch (true) {
    case $url === 'users' && $method === 'GET':
        require 'user/read.php';
        break;

    case $url === 'users' && $method === 'POST':
        require 'user/create.php';
        break;

    case preg_match('/^user\/\d+$/', $url) && $method === 'GET':
        require 'user/read_single.php';
        break;

    case preg_match('/^user\/\d+$/', $url) && $method === 'PUT':
        require 'user/update.php';
        break;

    case preg_match('/^user\/\d+$/', $url) && $method === 'DELETE':
        require 'user/delete.php';
        break;

    default:
        echo json_encode(['message' => 'Invalid endpoint']);
        break;
}
?>

 Routing with .htaccess

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]

Create New User – user/create.php

This endpoint handles POST requests to insert a new user record with image upload support.

<?php
require_once '../db.php';

// Check if all required fields are present
if (!isset($_POST['first_name'], $_POST['last_name'], $_POST['email'], $_POST['password'])) {
    echo json_encode(['error' => 'All fields are required']);
    exit;
}

// Sanitize input
$first_name = $conn->real_escape_string($_POST['first_name']);
$last_name = $conn->real_escape_string($_POST['last_name']);
$email = $conn->real_escape_string($_POST['email']);
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);

// Handle image upload
$image_name = null;
if (isset($_FILES['image']) && $_FILES['image']['error'] === 0) {
    $targetDir = "../uploads/";
    $image_name = time() . '_' . basename($_FILES["image"]["name"]);
    $targetFile = $targetDir . $image_name;
    
    if (!move_uploaded_file($_FILES["image"]["tmp_name"], $targetFile)) {
        echo json_encode(['error' => 'Image upload failed']);
        exit;
    }
}

// Insert into database
$sql = "INSERT INTO users (first_name, last_name, email, password, image)
        VALUES ('$first_name', '$last_name', '$email', '$password', '$image_name')";

if ($conn->query($sql)) {
    echo json_encode(['message' => 'User created successfully']);
} else {
    echo json_encode(['error' => 'Failed to create user']);
}
?>

Step 7: Get All Users – user/read.php

This script fetches and returns all user records from the MySQL database in JSON format.

<?php
require_once '../db.php';

$sql = "SELECT id, first_name, last_name, email, image FROM users ORDER BY id DESC";
$result = $conn->query($sql);

$users = [];

if ($result->num_rows > 0) {
    while ($row = $result->fetch_assoc()) {
        $users[] = [
            'id' => $row['id'],
            'first_name' => $row['first_name'],
            'last_name' => $row['last_name'],
            'email' => $row['email'],
            'image' => $row['image'] ? 'uploads/' . $row['image'] : null
        ];
    }
    echo json_encode($users);
} else {
    echo json_encode([]);
}
?>

Step 8: Get Single User – user/read_single.php

This endpoint returns a single user based on their ID. The ID should be passed as part of the URL (e.g. user/5).

<?php
require_once '../db.php';

// Get the user ID from the URL
$request_uri = $_SERVER['REQUEST_URI'];
$segments = explode('/', rtrim($request_uri, '/'));
$user_id = end($segments);

// Validate ID
if (!is_numeric($user_id)) {
    echo json_encode(['error' => 'Invalid user ID']);
    exit;
}

// Fetch user by ID
$sql = "SELECT id, first_name, last_name, email, image FROM users WHERE id = $user_id LIMIT 1";
$result = $conn->query($sql);

if ($result->num_rows > 0) {
    $row = $result->fetch_assoc();
    $user = [
        'id' => $row['id'],
        'first_name' => $row['first_name'],
        'last_name' => $row['last_name'],
        'email' => $row['email'],
        'image' => $row['image'] ? 'uploads/' . $row['image'] : null
    ];
    echo json_encode($user);
} else {
    echo json_encode(['error' => 'User not found']);
}
?>

Update User – user/update.php

This endpoint updates an existing user’s information by ID. The ID is passed in the URL (e.g. user/3) and the updated fields are sent as a PUT request.

<?php
require_once '../db.php';

// Get user ID from the URL
$uri = $_SERVER['REQUEST_URI'];
$segments = explode('/', rtrim($uri, '/'));
$user_id = end($segments);

// Parse PUT data
parse_str(file_get_contents("php://input"), $_PUT);

// Validate
if (!is_numeric($user_id)) {
    echo json_encode(['error' => 'Invalid user ID']);
    exit;
}

$first_name = isset($_PUT['first_name']) ? $conn->real_escape_string($_PUT['first_name']) : '';
$last_name  = isset($_PUT['last_name']) ? $conn->real_escape_string($_PUT['last_name']) : '';
$email      = isset($_PUT['email']) ? $conn->real_escape_string($_PUT['email']) : '';
$password   = isset($_PUT['password']) ? password_hash($_PUT['password'], PASSWORD_DEFAULT) : null;

$update_query = "UPDATE users SET 
    first_name = '$first_name',
    last_name = '$last_name',
    email = '$email'";

if ($password) {
    $update_query .= ", password = '$password'";
}

$update_query .= " WHERE id = $user_id";

// Run update
if ($conn->query($update_query)) {
    echo json_encode(['message' => 'User updated successfully']);
} else {
    echo json_encode(['error' => 'Update failed']);
}
?>

Delete User – user/delete.php

This endpoint deletes a user based on their ID. The ID is passed in the URL (e.g. user/3) and the request must be a DELETE method.

<?php
require_once '../db.php';

// Get user ID from URL
$request_uri = $_SERVER['REQUEST_URI'];
$segments = explode('/', rtrim($request_uri, '/'));
$user_id = end($segments);

// Validate ID
if (!is_numeric($user_id)) {
    echo json_encode(['error' => 'Invalid user ID']);
    exit;
}

// First delete the user's image (if any)
$image_sql = "SELECT image FROM users WHERE id = $user_id LIMIT 1";
$image_result = $conn->query($image_sql);
if ($image_result->num_rows > 0) {
    $row = $image_result->fetch_assoc();
    $image_path = '../uploads/' . $row['image'];
    if (file_exists($image_path) && !empty($row['image'])) {
        unlink($image_path); // delete image file
    }
}

// Delete user from database
$sql = "DELETE FROM users WHERE id = $user_id";
if ($conn->query($sql)) {
    echo json_encode(['message' => 'User deleted successfully']);
} else {
    echo json_encode(['error' => 'Failed to delete user']);
}
?>

📘 REST API Endpoint Reference

HTTP Method Endpoint Description
GET /users Fetch all users
POST /users Create a new user (with image upload)
GET /user/{id} Get a single user by ID
PUT /user/{id} Update a user by ID
DELETE /user/{id} Delete a user by ID

🔁 Sample Requests & Responses

1. GET /users

{
  "id": 1,
  "first_name": "John",
  "last_name": "Doe",
  "email": "john@example.com",
  "image": "uploads/1685529432_john.jpg"
}

2. GET /user/1

{
  "id": 1,
  "first_name": "John",
  "last_name": "Doe",
  "email": "john@example.com",
  "image": "uploads/1685529432_john.jpg"
}

3. GET /user/999 (Invalid ID)

{
  "error": "User not found"
}

4. POST /users (Form Data)

Form fields: first_name, last_name, email, password, image

{
  "message": "User created successfully"
}

5. POST /users (Missing Fields)

{
  "error": "All fields are required"
}

6. PUT /user/1

Request: first_name, last_name, email, password

{
  "message": "User updated successfully"
}

7. PUT /user/xyz (Invalid ID)

{
  "error": "Invalid user ID"
}

8. DELETE /user/2

{
  "message": "User deleted successfully"
}

9. DELETE /user/777

{
  "error": "Failed to delete user"
}

10. GET /unknown/route

{
  "message": "Invalid endpoint"
}

✅ Connect to Any Frontend

This REST API can now be used with Angular, React, Vue, Flutter, or even Postman for testing. Just call the appropriate endpoint with the required data.

Conclusion

You now have a fully functional REST API built with PHP, MySQL, .htaccess routing, image upload support, and CORS enabled. You can now connect this API to any frontend like Angular, React, Vue, or even mobile apps.

Leave a Reply

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