GPTT Developer Blog

← Back to Home

Building REST APIs with PHP: From Beginner to Production

📖 2,245 Words

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.

What you will build: A structured PHP REST API that can communicate with web applications, Flutter applications, mobile applications, dashboards, and other software systems.

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.

Key takeaway: A production-ready REST API is more than a PHP endpoint that returns JSON. It combines clear API design, secure database access, authentication, validation, proper HTTP responses, error handling, testing, and maintainable project architecture.

Article Statistics
📖 2,245 Words
Back to Top