Creating a RESTful API in PHP

 

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

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 

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

Ans so on.

There are 2 issues with this approach.

  1.  it’s ridiculously boring and time consuming to create a new file for each user
  2. 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

  1. all the urls are being handled with one file
  2. 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.

Comments

Popular posts from this blog

CRUD operation in Database using PHP