AI Reading
Quick summary of this article
This guide walks through building a complete REST API using PHP and MySQL. It covers creating a database, setting up clean URL routing with .htaccess, enabling CORS for cross-origin requests, and implementing full CRUD operations (Create, Read, Update, Delete) for a user management system. The API also supports image uploads and returns JSON responses, making it ready to connect with any modern frontend framework like Angular, React, or Vue.
- Database Setup: The tutorial provides SQL to create a
user_apidatabase with auserstable containing fields for name, email, password, and an optional image. - Core API Structure: The project includes a main
index.phpentry point that handles CORS headers, parses requests, and routes them to separate files for each CRUD operation (create, read, update, delete). - Clean URLs via .htaccess: An .htaccess file rewrites URLs like
/usersor/user/1to the main index file, enabling a clean and simple API endpoint structure. - Security Features: Passwords are securely hashed using PHP's
password_hash, and input data is sanitized withreal_escape_stringto prevent SQL injection. - Complete Endpoint Reference: The article includes a full list of API endpoints (e.g., GET /users, POST /users, PUT /user/{id}, DELETE /user/{id}) with sample JSON requests and responses for testing.
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.
