PHP simple REST API
Last Updated on Feb 15, 2023
Introduction
Last week rapid_api published a very good tutorial thread about creating a REST API with node.js and express. I want to help you develop the same simple REST API with PHP.
First of all if you don’t know about REST API make sure to check out this twitter thread
Goal
Before we start I’d like to mention that when I was writing this thread I wanted to make sure:
- I use pure PHP and no frameworks
- I use the simplest functions and structures so everyone can understand and follow
- I separate the main parts
Now let’s get started
Preparation
On my local machine I have created a folder called api in xampp > htdocs and inside it there is a file called index.php
If you don’t have xampp or you don’t know how to get started with php make sure to check this post
Now if you go to localhost/api you will get an empty response because the index.php is empty.
Pretty URL
The very first thing that we need to take care of is the urls in our project
One of the key features of REST API is the way each url is responsible for one resource and one action
Problem
At the moment if I create a users.php then I have to go to
localhost/api/users.php
Then for each id of a user I have to create a new file
localhost/api/users/1.php
localhost/api/users/2.php
And so on.
There are 2 issues with this approach.
- it’s ridiculously boring and time consuming to create a new file for each user
- The routes are ugly. All of them have .php at the end
Solution
Let’s solve that.
As I mentioned I don’t want to use any framework and I want to use the simplest and most understandable approach
So let’s see how we can take care of that
In api folder create a file named .htaccess and copy the following text
RewriteEngine On
RewriteBase /api
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)$ index.php [QSA,L]
We are telling our server to redirect all the request to /api and send them to index.php file
Now all the urls go to index.php for example all the following urls are going to index.php
api/users
api/users/10
api/users/5
Now we have solved both of the problems
- all the urls are being handled with one file
- urls are pretty and there is no .php at the end
URI
But how to check what uri user has requested?
Easy with $_SERVER superglobal variable Let’s see some examples
// url api/users
echo $_SERVER['REQUEST_URI'];
// /api/users
// url api/users/5
echo $_SERVER['REQUEST_URI'];
// /api/users/5
// url api
echo $_SERVER['REQUEST_URI'];
// /api
See it’s exactly what we need.
Now with a simple if or switch we can handle different paths
If you’ve never worked with conditionals check this post.
METHOD
The next thing we need to get from the request is the method of the request to see if it’s GET,POST,PUT,PATCH or DELETE
And you can get that from the $_SERVER superglobal as well
$_SERVER['REQUEST_METHOD']
Let’s store both of those values in variables
$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
We can use both of those variables in a simple switch statement to handle different requests
We need to check the following requests
- GET request to api/users
- GET request to api/users/{id}
- POST request to api/users
- PUT request to api/users/{id}
- DELETE request api/users/{id}
So let’s write the switch statement of all of these requests
switch ($method | $uri) {
/*
* Path: GET /api/users
* Task: show all the users
*/
case ($method == 'GET' && $uri == '/api/users'):
break;
/*
* Path: GET /api/users/{id}
* Task: get one user
*/
case ($method == 'GET' && preg_match('/\/api\/users\/[1-9]/', $uri)):
break;
/*
* Path: POST /api/users
* Task: store one user
*/
case ($method == 'POST' && $uri == '/api/users'):
break;
/*
* Path: PUT /api/users/{id}
* Task: update one user
*/
case ($method == 'PUT' && preg_match('/\/api\/users\/[1-9]/', $uri)):
break;
/*
* Path: DELETE /api/users/{id}
* Task: delete one user
*/
case ($method == 'DELETE' && preg_match('/\/api\/users\/[1-9]/', $uri)):
break;
/*
* Path: ?
* Task: this path doesn't match any of the defined paths
* throw an error
*/
default:
break;
}
When we want to use 2 variables in switch we use them with | between them
To know how preg_match works check out this post
Database
Now about the data. The best way is to store the data in a database but for this tutorial I didn’t want to use a database. So instead I use a json file as my “database” to have consistency for our data
My json file looks like this:
{
"1": "Pratham",
"2": "Amir"
}
To learn how to work with json checkout this post
I load the json data and convert them to array and work with them in my file and if I wanted to change the data I will change the array to json and write the json to the file
To read the whole file as one and store it in a variable I use
file_get_contents($jsonFile);
And to write the json to file I use
file_put_contents($jsonFile, $data);
Ok now that our database is taken care of let’s start with all the paths
I use postman to send the request and see the responses
GET ALL
Let's get all the users
case ($method == 'GET' && $uri == '/api/users'):
header('Content-Type: application/json');
echo json_encode($users, JSON_PRETTY_PRINT);
break;
GET ONE
Let's get one of the users
case ($method == 'GET' && preg_match('/\/api\/users\/[1-9]/', $uri)):
header('Content-Type: application/json');
// get the id
$id = basename($uri);
if (!array_key_exists($id, $users)) {
http_response_code(404);
echo json_encode(['error' => 'user does not exist']);
break;
}
$responseData = [$id => $users[$id]];
echo json_encode($responseData, JSON_PRETTY_PRINT);
break;
Basename($uri) gives me the last part of the uri. For example if uri is api/users/10 it returns 10.
Then with array_key_exists I check if there is a user with id 10
STORE
Let's add a new user
case ($method == 'POST' && $uri == '/api/users'):
header('Content-Type: application/json');
$requestBody = json_decode(file_get_contents('php://input'), true);
$name = $requestBody['name'];
if (empty($name)) {
http_response_code(404);
echo json_encode(['error' => 'Please add name of the user']);
}
$users[] = $name;
$data = json_encode($users, JSON_PRETTY_PRINT);
file_put_contents($jsonFile, $data);
echo json_encode(['message' => 'user added successfully']);
break;
With file_get_contents('php://input') I can get the body of the request and since in this case it’s json I will decode the json so I can get the name.
UPDATE
Let's update one of the users
case ($method == 'PUT' && preg_match('/\/api\/users\/[1-9]/', $uri)):
header('Content-Type: application/json');
// get the id
$id = basename($uri);
if (!array_key_exists($id, $users)) {
http_response_code(404);
echo json_encode(['error' => 'user does not exist']);
break;
}
$requestBody = json_decode(file_get_contents('php://input'), true);
$name = $requestBody['name'];
if (empty($name)) {
http_response_code(404);
echo json_encode(['error' => 'Please add name of the user']);
}
$users[$id] = $name;
$data = json_encode($users, JSON_PRETTY_PRINT);
file_put_contents($jsonFile, $data);
echo json_encode(['message' => 'user updated successfully']);
break;
DELETE
Let's delete one of the users
case ($method == 'DELETE' && preg_match('/\/api\/users\/[1-9]/', $uri)):
header('Content-Type: application/json');
// get the id
$id = basename($uri);
if (empty($users[$id])) {
http_response_code(404);
echo json_encode(['error' => 'user does not exist']);
break;
}
unset($users[$id]);
$data = json_encode($users, JSON_PRETTY_PRINT);
file_put_contents($jsonFile, $data);
echo json_encode(['message' => 'user deleted successfully']);
break;
Final File
Now our index.php file looks like this
In 70 lines of codes we could create a RESTful API in PHP. isn’t it amazing?!
<?php
$jsonFile = 'users.json';
$data = file_get_contents($jsonFile);
$users = json_decode($data, true);
$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
switch ($method | $uri) {
case ($method == 'GET' && $uri == '/api/users'):
header('Content-Type: application/json');
echo json_encode($users, JSON_PRETTY_PRINT);
break;
case ($method == 'GET' && preg_match('/\/api\/users\/[1-9]/', $uri)):
header('Content-Type: application/json');
$id = basename($uri);
if (!array_key_exists($id, $users)) {
http_response_code(404);
echo json_encode(['error' => 'user does not exist']);
break;
}
$responseData = [$id => $users[$id]];
echo json_encode($responseData, JSON_PRETTY_PRINT);
break;
case ($method == 'POST' && $uri == '/api/users'):
header('Content-Type: application/json');
$requestBody = json_decode(file_get_contents('php://input'), true);
$name = $requestBody['name'];
if (empty($name)) {
http_response_code(404);
echo json_encode(['error' => 'Please add name of the user']);
}
$users[] = $name;
$data = json_encode($users, JSON_PRETTY_PRINT);
file_put_contents($jsonFile, $data);
echo json_encode(['message' => 'user added successfully']);
break;
case ($method == 'PUT' && preg_match('/\/api\/users\/[1-9]/', $uri)):
header('Content-Type: application/json');
$id = basename($uri);
if (!array_key_exists($id, $users)) {
http_response_code(404);
echo json_encode(['error' => 'user does not exist']);
break;
}
$requestBody = json_decode(file_get_contents('php://input'), true);
$name = $requestBody['name'];
if (empty($name)) {
http_response_code(404);
echo json_encode(['error' => 'Please add name of the user']);
}
$users[$id] = $name;
$data = json_encode($users, JSON_PRETTY_PRINT);
file_put_contents($jsonFile, $data);
echo json_encode(['message' => 'user updated successfully']);
break;
case ($method == 'DELETE' && preg_match('/\/api\/users\/[1-9]/', $uri)):
header('Content-Type: application/json');
$id = basename($uri);
if (empty($users[$id])) {
http_response_code(404);
echo json_encode(['error' => 'user does not exist']);
break;
}
unset($users[$id]);
$data = json_encode($users, JSON_PRETTY_PRINT);
file_put_contents($jsonFile, $data);
echo json_encode(['message' => 'user deleted successfully']);
break;
default:
http_response_code(404);
echo json_encode(['error' => "We cannot find what you're looking for."]);
break;
}
Bonus
In this case I didn’t want all my users to be deleted so I added a new condition that if only one user is left don’t let it be deleted. Like this
if (sizeof($users) == 1){
http_response_code(404);
echo json_encode(['error' => 'there is only one user left. you cannot delete it!']);
break;
}
Source Code
You can see the fully commented source code plus the post man collection on my github
Conclusion
Now you know how to create a simple RESTful API in PHP.
I recommend you to open a PHP files and go through all the steps we went and add another resource like posts.
If you have any suggestions, questions, or opinions, please contact me. I’m looking forward to hearing from you!
Key takeaways
- Create a RESTful API in PHP with no framework
- Pretty URL in PHP
- Handle request's body
- use Json file as your database
- Switch with multiple variables