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.