Building REST APIs with PHP: From Beginner to Production
REST APIs are an important part of modern web and mobile applications. They allow different applications and services to communicate with each other using standardized HTTP requests and responses.
In this practical guide, we will build a PHP REST API from the basic concepts up to a more production-oriented architecture. The examples cover HTTP methods, JSON responses, MySQL integration, CRUD operations, validation, authentication, security, error handling, and API testing.
1. What is a REST API?
REST stands for Representational State Transfer. It is an architectural style commonly used for designing APIs that communicate over HTTP.
A REST API exposes resources through URLs and allows clients to interact with those resources using HTTP methods.
Client
↓
HTTP Request
↓
REST API
↓
Business Logic
↓
Database
↓
JSON Response
↓
Client
2. Understanding HTTP Methods
Each HTTP method has a specific purpose when working with API resources.
- GET - Retrieve data
- POST - Create new data
- PUT - Update existing data
- PATCH - Partially update data
- DELETE - Remove data
For example, a users API might use the following endpoints:
GET /api/users
GET /api/users/1
POST /api/users
PUT /api/users/1
DELETE /api/users/1
3. Basic PHP REST API Structure
A small API can begin with a simple structure. As the application grows, the code can later be separated into controllers, models, routes, and middleware.
api/
├── users.php
├── products.php
├── auth.php
└── config.php
4. Setting the JSON Content Type
REST APIs commonly return JSON because it is lightweight and supported by browsers, mobile applications, and most programming languages.
<?php
header('Content-Type: application/json');
$data = [
'status' => 'success',
'message' => 'API is working'
];
echo json_encode($data);
5. Handling HTTP Requests
PHP provides $_SERVER['REQUEST_METHOD'] for determining
which HTTP method was used.
<?php
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
// Fetch data
break;
case 'POST':
// Create data
break;
case 'PUT':
// Update data
break;
case 'DELETE':
// Delete data
break;
default:
http_response_code(405);
echo json_encode([
'status' => 'error',
'message' => 'Method not allowed'
]);
}
6. Creating a Reusable JSON Response
Consistent responses make an API easier to consume, debug, and maintain.
<?php
function jsonResponse(
array $data,
int $statusCode = 200
): void {
http_response_code($statusCode);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
This function can then be reused throughout the API.
jsonResponse([
'status' => 'success',
'message' => 'Request completed successfully'
]);
7. Connecting PHP to MySQL
Many REST APIs use a relational database to store application data. PHP can connect to MySQL using the MySQLi extension.
<?php
$conn = new mysqli(
'localhost',
'user',
'password',
'database'
);
if ($conn->connect_error) {
http_response_code(500);
echo json_encode([
'status' => 'error',
'message' => 'Database connection failed'
]);
exit;
}
Database credentials should not be exposed inside publicly accessible application files. In production systems, configuration should be separated from application logic.
8. Designing a Users Table
Before creating CRUD endpoints, the API needs a database structure.
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(150) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
The password column should store a password hash rather than a user's original password.
9. Reading Data from MySQL
A GET endpoint can retrieve records and return them as JSON.
<?php
$stmt = $conn->prepare(
'SELECT id, name, email, created_at FROM users'
);
$stmt->execute();
$result = $stmt->get_result();
$data = [];
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
echo json_encode([
'status' => 'success',
'data' => $data
]);
10. Getting a Single Resource
APIs often need to retrieve one specific resource using its ID.
<?php
$id = filter_input(
INPUT_GET,
'id',
FILTER_VALIDATE_INT
);
if (!$id) {
http_response_code(400);
echo json_encode([
'status' => 'error',
'message' => 'Invalid user ID'
]);
exit;
}
$stmt = $conn->prepare(
'SELECT id, name, email FROM users WHERE id = ?'
);
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
if (!$user) {
http_response_code(404);
echo json_encode([
'status' => 'error',
'message' => 'User not found'
]);
exit;
}
echo json_encode([
'status' => 'success',
'data' => $user
]);
11. Using Prepared Statements
Prepared statements should be used when working with values supplied by users or external applications. They help protect database queries against SQL injection.
<?php
$stmt = $conn->prepare(
'SELECT * FROM users WHERE email = ?'
);
$stmt->bind_param('s', $email);
$stmt->execute();
12. Reading JSON Request Data
Modern applications commonly send request bodies as JSON.
<?php
$input = json_decode(
file_get_contents('php://input'),
true
);
$name = trim($input['name'] ?? '');
$email = trim($input['email'] ?? '');
The API should validate the decoded data before attempting database operations.
13. Validating Request Data
Never assume that data received from an API client is valid. Validation should happen before database operations.
<?php
if ($name === '') {
http_response_code(422);
echo json_encode([
'status' => 'error',
'message' => 'Name is required'
]);
exit;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
http_response_code(422);
echo json_encode([
'status' => 'error',
'message' => 'Invalid email address'
]);
exit;
}
14. Creating a Record with POST
A POST request can be used to create a new user.
<?php
$input = json_decode(
file_get_contents('php://input'),
true
);
$name = trim($input['name'] ?? '');
$email = trim($input['email'] ?? '');
if ($name === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
http_response_code(422);
echo json_encode([
'status' => 'error',
'message' => 'Invalid input'
]);
exit;
}
$stmt = $conn->prepare(
'INSERT INTO users (name, email) VALUES (?, ?)'
);
$stmt->bind_param(
'ss',
$name,
$email
);
$stmt->execute();
http_response_code(201);
echo json_encode([
'status' => 'success',
'message' => 'User created successfully',
'id' => $stmt->insert_id
]);
15. Updating a Record with PUT
PUT can be used when an existing resource needs to be updated.
<?php
$id = (int) ($input['id'] ?? 0);
$name = trim($input['name'] ?? '');
if ($id <= 0 || $name === '') {
http_response_code(422);
echo json_encode([
'status' => 'error',
'message' => 'Invalid request data'
]);
exit;
}
$stmt = $conn->prepare(
'UPDATE users SET name = ? WHERE id = ?'
);
$stmt->bind_param(
'si',
$name,
$id
);
$stmt->execute();
echo json_encode([
'status' => 'success',
'message' => 'User updated successfully'
]);
16. Deleting a Record
DELETE can be used to remove an existing resource.
<?php
$id = (int) ($input['id'] ?? 0);
$stmt = $conn->prepare(
'DELETE FROM users WHERE id = ?'
);
$stmt->bind_param(
'i',
$id
);
$stmt->execute();
echo json_encode([
'status' => 'success',
'message' => 'User deleted successfully'
]);
17. Password Hashing
If the API handles authentication, passwords should never be stored as plain text.
<?php
$passwordHash = password_hash(
$password,
PASSWORD_DEFAULT
);
Password verification can then be performed using:
<?php
if (password_verify($password, $passwordHash)) {
// Password is correct
} else {
// Invalid password
}
18. API Authentication
Private API resources should be protected using an authentication mechanism appropriate for the application.
- API Keys
- JWT Tokens
- OAuth
- Session-based authentication
Authentication answers the question: "Who is making this request?"
Authorization answers: "Is this user allowed to perform this action?"
19. Authorization
Authentication alone is not enough. An API should also check whether the authenticated user has permission to access a resource.
Authenticated User
↓
Check Identity
↓
Check Permission
↓
Allow or Deny Request
20. HTTP Status Codes
REST APIs should return appropriate HTTP status codes so clients can understand the result of a request.
- 200 - Successful request
- 201 - Resource created
- 204 - Successful request with no response body
- 400 - Invalid request
- 401 - Authentication required
- 403 - Access denied
- 404 - Resource not found
- 409 - Resource conflict
- 422 - Validation error
- 500 - Server error
21. Standard Error Response
A consistent error structure makes client-side development easier.
{
"status": "error",
"message": "Validation failed",
"errors": {
"email": "Invalid email address"
}
}
22. Example Successful Response
{
"status": "success",
"data": [
{
"id": 1,
"name": "John",
"email": "john@example.com"
},
{
"id": 2,
"name": "Anna",
"email": "anna@example.com"
}
]
}
23. API Resource Design
API URLs should represent resources rather than actions whenever possible.
For example:
GET /api/users
GET /api/users/10
POST /api/users
PUT /api/users/10
DELETE /api/users/10
This structure makes the API predictable and easier for other developers to understand.
24. API Validation Flow
HTTP Request
↓
Read Input
↓
Validate Data
↓
Authenticate
↓
Authorize
↓
Database Operation
↓
Generate Response
↓
Return JSON
25. API Security Best Practices
- Validate all incoming data
- Use prepared SQL statements
- Use HTTPS
- Protect authentication credentials
- Implement authorization
- Use secure password hashing
- Return appropriate HTTP status codes
- Limit excessive API requests
- Avoid exposing sensitive server information
- Log important server-side errors
- Keep dependencies updated
26. Handling API Errors
Production APIs should not expose raw database errors or sensitive server information to clients.
try {
// Database or application operation
} catch (Throwable $e) {
error_log($e->getMessage());
http_response_code(500);
echo json_encode([
'status' => 'error',
'message' => 'Internal server error'
]);
}
27. API Rate Limiting
Public APIs may receive a large number of requests. Rate limiting can help reduce abuse and excessive resource consumption.
Client
↓
Request #1
Request #2
Request #3
↓
Rate Limit Check
↓
Allow / Reject
Rate limiting can be implemented using application logic, reverse proxies, API gateways, hosting infrastructure, or other dedicated services.
28. CORS Considerations
If a browser-based application hosted on another origin communicates with the API, Cross-Origin Resource Sharing may need to be configured.
Access-Control-Allow-Origin: https://example.com
Avoid allowing every origin in production unless the application genuinely requires that configuration.
29. API Pagination
Returning thousands of records in one request can make an API slower and increase memory usage. Pagination allows clients to retrieve smaller groups of records.
GET /api/users?page=1&limit=20
A response can include pagination information:
{
"status": "success",
"data": [],
"pagination": {
"page": 1,
"limit": 20,
"total": 150
}
}
30. API Filtering and Searching
APIs can also provide controlled filtering and searching capabilities.
GET /api/users?search=john
GET /api/products?category=computer
GET /api/records?status=active
Query parameters should always be validated before being used in database queries.
31. API Versioning
Versioning can help maintain compatibility when an API changes.
/api/v1/users
/api/v2/users
This approach allows an older client to continue using a previous API version while newer applications migrate to a newer version.
32. Organizing a Production API
As the application grows, separating responsibilities makes the project easier to maintain.
api/
│
├── config/
│ └── database.php
│
├── controllers/
│ ├── UserController.php
│ └── ProductController.php
│
├── models/
│ ├── User.php
│ └── Product.php
│
├── middleware/
│ └── auth.php
│
├── routes/
│ └── api.php
│
├── helpers/
│ └── response.php
│
└── index.php
33. Separating Responsibilities
A maintainable API should avoid placing database queries, validation, authentication, and response formatting into one large PHP file.
Request
↓
Route
↓
Controller
↓
Validation
↓
Service / Business Logic
↓
Model / Database
↓
Response
34. Connecting a Flutter Application
A REST API can serve as a backend for Flutter applications.
Flutter Application
↓
HTTP Request
↓
PHP REST API
↓
MySQL Database
↓
JSON Response
↓
Flutter Application
The same API can also serve a web application or another mobile application.
35. Connecting Multiple Clients
┌── Flutter App
│
├── Web Application
│
├── Mobile Application
│
└── Admin Dashboard
↓
PHP REST API
↓
MySQL
36. Testing the API
API endpoints should be tested with valid and invalid requests. Testing should cover different HTTP methods, authentication states, validation scenarios, and database operations.
- Test successful GET requests
- Test POST validation
- Test PUT updates
- Test DELETE operations
- Test invalid IDs
- Test missing parameters
- Test unauthorized requests
- Test database errors
- Test malformed JSON
37. Example API Testing Workflow
API Client
↓
Send Request
↓
Check HTTP Status
↓
Check JSON Response
↓
Verify Database
↓
Test Error Cases
↓
Document Result
38. Production API Checklist
- HTTPS enabled
- Prepared statements implemented
- Input validation implemented
- Password hashing implemented
- Authentication implemented
- Authorization implemented
- Error handling implemented
- HTTP status codes used correctly
- Database credentials protected
- API responses standardized
- Rate limiting considered
- Logging implemented
- API endpoints tested
39. Complete REST API Architecture
Client Application
↓
HTTPS Request
↓
API Router
↓
Authentication
↓
Authorization
↓
Validation
↓
Controller
↓
Business Logic
↓
Database Model
↓
MySQL
↓
JSON Response
↓
Client Application
40. From Beginner API to Production API
Basic PHP Script
↓
JSON Response
↓
HTTP Methods
↓
Database
↓
CRUD Operations
↓
Prepared Statements
↓
Validation
↓
Authentication
↓
Authorization
↓
Error Handling
↓
Pagination
↓
Rate Limiting
↓
Logging
↓
API Testing
↓
Production REST API
41. Practical Mini Project
A useful way to apply the concepts in this tutorial is to build a simple user management API.
The project can include the following features:
- Create users
- List users
- View a single user
- Update users
- Delete users
- Validate request data
- Authenticate users
- Protect private endpoints
- Return standardized JSON responses
42. Example Mini Project Flow
Flutter / Web Client
↓
POST /api/users
↓
PHP Router
↓
Validate Request
↓
User Controller
↓
User Model
↓
MySQL
↓
201 Created
↓
JSON Response
43. Common Problems
Database Connection Error
Check the database host, username, password, database name, and server configuration.
Invalid JSON
Verify that the client is sending valid JSON and that the request contains the appropriate content type.
401 Unauthorized
Check whether the authentication credentials or token are missing or invalid.
403 Forbidden
The client may be authenticated but does not have permission to perform the requested action.
404 Not Found
Check the requested endpoint and resource ID.
500 Internal Server Error
Inspect server-side logs rather than exposing internal errors to the API client.
44. Best Practices
- Keep API endpoints predictable
- Use meaningful resource names
- Validate all input
- Use prepared statements
- Separate business logic from database code
- Protect sensitive configuration
- Use HTTPS
- Implement authentication and authorization
- Use consistent JSON responses
- Document API endpoints
- Test both successful and failed requests
- Keep dependencies updated
45. Final API Workflow
Client Application
│
▼
HTTP / HTTPS Request
│
▼
PHP REST API
│
├── Routing
│
├── Authentication
│
├── Authorization
│
├── Validation
│
├── Business Logic
│
└── Error Handling
│
▼
MySQL Database
│
▼
PHP Processing
│
▼
JSON Response
│
▼
Client Application
Conclusion
Building a REST API with PHP starts with understanding HTTP requests, JSON responses, database operations, and API structure. As the project becomes more advanced, security, authentication, validation, error handling, pagination, logging, and maintainable architecture become increasingly important.
A well-designed PHP REST API can serve as the backend for websites, Flutter applications, mobile applications, dashboards, and other software systems.
The most important lesson is that a production API should not simply return JSON. It should have a predictable structure, secure database operations, validated input, appropriate authentication, proper authorization, useful error responses, and an architecture that can grow with the application.
📖 2,245 Words