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 === 'POST':
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 Database :- db.php
<?php
$host = 'localhost';
$db_name = 'user_api';
$username = 'admin';
$password = 'admin';
$conn = new mysqli($host, $username, $password, $db_name);
if ($conn->connect_error) {
die(json_encode([
'success' => false,
'message' => 'Database Connection Failed: ' . $conn->connect_error
]));
}
$conn->set_charset("utf8");
?>
Create New User – user/create.php
This endpoint handles POST requests to insert a new user record with image upload support.
<?php
require_once __DIR__ . '/../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 = " __DIR__ . '/../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 __DIR__ . '/../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 __DIR__ . '/../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 __DIR__ . '/../db.php';
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json");
// Log method and content type (for debugging)
$method = $_SERVER["REQUEST_METHOD"];
$contentType = $_SERVER["CONTENT_TYPE"] ?? '';
// Only allow POST with multipart/form-data
if ($method !== 'POST' || !str_contains($contentType, 'multipart/form-data')) {
echo json_encode(['error' => 'Unsupported request type']);
exit;
}
// ✅ Extract user ID from URL (like user/1)
$id = null;
if (isset($_GET['url']) && preg_match('/^user\/(\d+)$/', $_GET['url'], $matches)) {
$id = (int)$matches[1];
} else {
echo json_encode(['error' => 'User ID not found in URL']);
exit;
}
// Process incoming form fields
$data = $_POST;
$image_name = null;
// Handle optional image upload
if (isset($_FILES['image']) && $_FILES['image']['error'] === 0) {
$targetDir = __DIR__ . '/../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;
}
}
// Sanitize and validate input
$first_name = $conn->real_escape_string($data['first_name'] ?? '');
$last_name = $conn->real_escape_string($data['last_name'] ?? '');
$email = $conn->real_escape_string($data['email'] ?? '');
$password = !empty($data['password']) ? password_hash($data['password'], PASSWORD_DEFAULT) : null;
if (!$first_name || !$last_name || !$email) {
echo json_encode(['error' => 'First name, last name, and email are required']);
exit;
}
// Build SQL update query
$fields = [
"first_name = '$first_name'",
"last_name = '$last_name'",
"email = '$email'"
];
if ($password) {
$fields[] = "password = '$password'";
}
if ($image_name) {
$fields[] = "image = '$image_name'";
}
$set_clause = implode(", ", $fields);
$sql = "UPDATE users SET $set_clause WHERE id = $id";
// Run query
if ($conn->query($sql)) {
echo json_encode(['message' => 'User updated successfully']);
} else {
echo json_encode(['error' => 'Failed to update user']);
}
?>
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__DIR__ . '/../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.
