Press "Enter" to skip to content

PHP – how to build very simple REST API? Step by step instructions.

If you need to build very simple REST API with PHP you are in right place! I needed some time ago to build very simple REST API in PHP. I had to use PHP – this was the only back-end technology in my project and at that moment, my skills were pure in PHP. I was looking for a very simple tutorial to have endpoint in normal form like http://my-domain.com/posts/12 just for a very basic functionality. Then I found on the internet big tutorials using some PHP frameworks to build great and fully proper REST APIs in PHP.

But I didn’t want to spend a few days on learning object programming in PHP, learning frameworks etc. I needed to do quick solution in good enough (not perfect) shape. So I started plying with PHP and in result, I created totally light PHP REST API. Read further to know how to do it. First we will build the simpliest possible example with mapping URL path and invoking methods. Second example will be still easy, but a little more sophisticated, and it will show how to build easy but fully working REST API with PHP.

Example 1 – the simplest REST API in PHP

  1. Open code of this example on our Gitlab account HERE.
  2. The first, probably the most important thing, is that we want to use nice-looking path to call our API, like: http://my-domain.com/customers/11 , not the url GET parameters instead of nice path.We need to create URL address mapping in .htaccess file, its content is very short but works perfectly – it turns nice-looking URL path into GET parameters to make it easy to read by PHP (further below, in index.php file).
    .htaccess

    RewriteEngine On
    RewriteRule ^(.+)(\?.+)?$ index.php?path=$1 [L,QSA]
  3. Next, we need to have some dummy data just to show it in browser for testing. In reality, in this place you will connect to data base to get some data or you will do some operations based on user input.
    storage.php

    <?php
    
      function set_faked_data () {  
        $orders_data_json = '[{"index":0,"price":135,"product":"motherboard","owner":"LOCAZONE"},{"index":1,"price":138,"product":"cpu","owner":"MULTRON"},{"index":2,"price":278,"product":"cpu","owner":"TRIBALOG"},{"index":3,"price":202,"product":"motherboard","owner":"ANDRYX"},{"index":4,"price":87,"product":"hid","owner":"CONFRENZY"},{"index":5,"price":355,"product":"gpu","owner":"COMVEX"},{"index":6,"price":348,"product":"gpu","owner":"GLEAMINK"},{"index":7,"price":360,"product":"hid","owner":"OHMNET"},{"index":8,"price":300,"product":"hid","owner":"CENTURIA"},{"index":9,"price":351,"product":"hid","owner":"JOVIOLD"},{"index":10,"price":40,"product":"cpu","owner":"ELECTONIC"},{"index":11,"price":251,"product":"cpu","owner":"SIGNIDYNE"},{"index":12,"price":272,"product":"audio","owner":"MANGLO"}]';
        $orders_data = json_decode($orders_data_json, true);
    
        $_SESSION["orders_data"] = $orders_data;
    
      }
    
    ?>
  4. Next, we must have some function which will take all ORDERS data or single item from array and return it to client (browser):
    orders.php

    <?php
    
      /**
       * ORDERS
       */
    
      function get_orders ($order_index = "") {
        if($order_index) {
          foreach($_SESSION["orders_data"] as $order) {
            if($order["index"] == $order_index) {
              return $order;
            }
          }
    
          return null;
        } else {
          return $_SESSION["orders_data"];
        }
        
      }
    
      function create_new_order ($data) {
        array_push($_SESSION["orders_data"], json_decode($data, true));
        return $_SESSION["orders_data"];
      }
    
    ?>

    First method get_orders ($order_index = "") is returning all orders – when argument $order_index  is empty, if order_index has some value, then it returns one single order element.

    Second method create_new_order ($data) adds new order to session variable “orders_data” based on JSON from user input.

  5. Next, we need additional file with helpers functions. There will be only one function now:
    _shared.php

    <?php
    
      function header_status($statusCode) {
        static $status_codes = null;
      
        if ($status_codes === null) {
            $status_codes = array (
                100 => 'Continue',
                101 => 'Switching Protocols',
                102 => 'Processing',
                200 => 'OK',
                201 => 'Created',
                202 => 'Accepted',
                203 => 'Non-Authoritative Information',
                204 => 'No Content',
                205 => 'Reset Content',
                206 => 'Partial Content',
                207 => 'Multi-Status',
                299 => 'Fake 4xx error from middleware API',
                300 => 'Multiple Choices',
                301 => 'Moved Permanently',
                302 => 'Found',
                303 => 'See Other',
                304 => 'Not Modified',
                305 => 'Use Proxy',
                307 => 'Temporary Redirect',
                400 => 'Bad Request',
                401 => 'Unauthorized',
                402 => 'Payment Required',
                403 => 'Forbidden',
                404 => 'Not Found',
                405 => 'Method Not Allowed',
                406 => 'Not Acceptable',
                407 => 'Proxy Authentication Required',
                408 => 'Request Timeout',
                409 => 'Conflict',
                410 => 'Gone',
                411 => 'Length Required',
                412 => 'Precondition Failed',
                413 => 'Request Entity Too Large',
                414 => 'Request-URI Too Long',
                415 => 'Unsupported Media Type',
                416 => 'Requested Range Not Satisfiable',
                417 => 'Expectation Failed',
                422 => 'Unprocessable Entity',
                423 => 'Locked',
                424 => 'Failed Dependency',
                426 => 'Upgrade Required',
                500 => 'Internal Server Error',
                501 => 'Not Implemented',
                502 => 'Bad Gateway',
                503 => 'Service Unavailable',
                504 => 'Gateway Timeout',
                505 => 'HTTP Version Not Supported',
                506 => 'Variant Also Negotiates',
                507 => 'Insufficient Storage',
                509 => 'Bandwidth Limit Exceeded',
                510 => 'Not Extended'
            );
        }
      
        if ($status_codes[$statusCode] !== null) {
            $status_string = $statusCode . ' ' . $status_codes[$statusCode];
            header($_SERVER['SERVER_PROTOCOL'] . ' ' . $status_string, true, $statusCode);
        }
      }
    ?>

    So method header_status($statusCode) returns appropriate HTTP response header like “404 Not found”.

  6. OK, so now let’s move to the most important file in our API, and this will be, of course, index.php file:
    index.php

    <?php
    
      require_once '_shared.php';
      require_once 'storage.php';
      require_once 'orders.php';
    
      /**
       * RUN API HERE
       */
    
      set_faked_data();
      runApi();
    
      function decodePath() {
        if(!empty($_GET['path'])) {
          $path_parts = $_GET['path'];
        } else {
          $path_parts = false;
        }
        
        if(empty($path_parts)) {
          return false;
        }
    
        if(strrpos($path_parts, '/') === false) {
          $path_parts .= '/';
        }
    
        $path_parts = explode('/', $path_parts);
        
        if(empty($path_parts[count($path_parts) - 1])) {
          array_pop($path_parts);
        }
        
        return $path_parts;
      }
    
      function runApi () {
        /*
        * 1st param: method
        * 2nd param: detailed action
        * 3rd param: props
        */
        
        $data = file_get_contents("php://input");
        $path = decodePath();
        
        $http_method = $_SERVER["REQUEST_METHOD"];
        $method = isset($path[0]) && !empty($path[0]) ? $path[0] : null;
        $prop = isset($path[1]) && !empty($path[1]) ? $path[1] : null;
        $data = sizeof($data) ? $data : null;
        
        if($method === "orders") {
          switch($http_method) {
            case "GET":
              send_results(get_orders($prop));
              break;
            case "PUT":
              send_results(create_new_order($data));
              break;
          }
        } else {
          send_results("", 404);
        }
    
      }
    
    
    ?>

    Start reading from the top to the bottom. On the very beginning of the file we include 3 files created in steps above.
    We set dummy/fake data from storage.php file and this function will place ORDERS data in session variable.  runApi() function triggers a function which makes us our REST API in PHP working.

    Below, we can find decodePath() function which is very important – it is transforming GET parameters (taken from nice-looking path like http://my-domain.com/customers/11 by .htaccess file – look at step 1 at very top of the page).

    Below, there is a declaration of runApi() function. Inside, there is decoded URL path, and all GET parameters are transformed here into variables: $method, $prop and , we get the HTTP method used by client (browser) and sent data: $http_method, $data.

    So now we have everything to trigger a function from ORDERS.PHP file. We know what end point user called (/orders) and if there are additional parameters, like order index (for getting single order only).
    So we have if statement which checks the method name (/orders) and what was the HTTP method and sends parameters to right function from orders.php file.

  7. When you type URL to your REST API in PHP in your client (browser) like: http://localhost:80/php-api/orders you will see JSON with all ORDERS from storage.php file:
    simple REST API in PHP
  8. We can also use PUT method to insert data into storage ORDERS session variable (array). For this case we must use app like POSTMAN to make a PUT request. This method will return all ORDERS (with the new one). We can send JSON like that: {"index":13,"price":111111,"product":"CAR","owner":"TEEEEEEST"} with header Content-Type: application/json.
    simple REST API in PHP

 

Example 2 – full simple REST API in PHP

In this example we will create almost normal REST API in PHP which allows to build bigger back-end API for your solution. We will still reuse files from example 1. In this example we extend our API to work with longer URL path: /method/end-point/parameter.

Check code of this example on our Gitlab account HERE.

  1. In storage.php file we add new data set for CUSTOMERS:
    storage.php

    <?php
    
      function set_faked_data () {
        $customers_data_json = '[{"index":0,"age":20,"eyeColor":"blue","name":"Puckett Branch","gender":"male","company":"FIREWAX","email":"puckettbranch@firewax.com","phone":"+1 (968) 479-2314"},{"index":1,"age":31,"eyeColor":"green","name":"Shelia Boyd","gender":"female","company":"LOCAZONE","email":"sheliaboyd@locazone.com","phone":"+1 (995) 468-3653"},{"index":2,"age":37,"eyeColor":"green","name":"Tameka Pacheco","gender":"female","company":"JUNIPOOR","email":"tamekapacheco@junipoor.com","phone":"+1 (840) 412-2533"},{"index":3,"age":37,"eyeColor":"green","name":"Franks Holmes","gender":"male","company":"ZILLACTIC","email":"franksholmes@zillactic.com","phone":"+1 (968) 563-3318"},{"index":4,"age":23,"eyeColor":"blue","name":"Oneil Clayton","gender":"male","company":"AMTAS","email":"oneilclayton@amtas.com","phone":"+1 (998) 556-2196"},{"index":5,"age":28,"eyeColor":"blue","name":"Jenna Edwards","gender":"female","company":"CUIZINE","email":"jennaedwards@cuizine.com","phone":"+1 (805) 406-2471"},{"index":6,"age":24,"eyeColor":"blue","name":"Claire Strong","gender":"female","company":"TERASCAPE","email":"clairestrong@terascape.com","phone":"+1 (851) 561-3299"},{"index":7,"age":33,"eyeColor":"brown","name":"Spencer Reyes","gender":"male","company":"EXOTERIC","email":"spencerreyes@exoteric.com","phone":"+1 (897) 509-2920"},{"index":8,"age":26,"eyeColor":"blue","name":"Gretchen Farrell","gender":"female","company":"BLUPLANET","email":"gretchenfarrell@bluplanet.com","phone":"+1 (892) 579-3020"},{"index":9,"age":38,"eyeColor":"blue","name":"Lupe Campbell","gender":"female","company":"SNORUS","email":"lupecampbell@snorus.com","phone":"+1 (864) 521-2530"},{"index":10,"age":26,"eyeColor":"brown","name":"Boone Cortez","gender":"male","company":"BARKARAMA","email":"boonecortez@barkarama.com","phone":"+1 (978) 404-2534"},{"index":11,"age":31,"eyeColor":"green","name":"Leta Riley","gender":"female","company":"ATOMICA","email":"letariley@atomica.com","phone":"+1 (992) 434-2713"}]';
        $customers_data = json_decode($customers_data_json, true);
        
        $orders_data_json = '[{"index":0,"price":135,"product":"motherboard","owner":"LOCAZONE"},{"index":1,"price":138,"product":"cpu","owner":"MULTRON"},{"index":2,"price":278,"product":"cpu","owner":"TRIBALOG"},{"index":3,"price":202,"product":"motherboard","owner":"ANDRYX"},{"index":4,"price":87,"product":"hid","owner":"CONFRENZY"},{"index":5,"price":355,"product":"gpu","owner":"COMVEX"},{"index":6,"price":348,"product":"gpu","owner":"GLEAMINK"},{"index":7,"price":360,"product":"hid","owner":"OHMNET"},{"index":8,"price":300,"product":"hid","owner":"CENTURIA"},{"index":9,"price":351,"product":"hid","owner":"JOVIOLD"},{"index":10,"price":40,"product":"cpu","owner":"ELECTONIC"},{"index":11,"price":251,"product":"cpu","owner":"SIGNIDYNE"},{"index":12,"price":272,"product":"audio","owner":"MANGLO"}]';
        $orders_data = json_decode($orders_data_json, true);
    
        $_SESSION["customers_data"] = $customers_data;
        $_SESSION["orders_data"] = $orders_data;
    
      }
    
    ?>
  2. In _shared.php file with helpers we will add two methods responsible for returning the end data to client (browser) + we will create there a PHP Class (this is Object Oriented PHP) for handling end point’s methods (like from orders.php file from example 1).
  3. File containing ORDERS and CUSTOMERS function will also change into Object Oriented, and will look like:
    order.php

    <?php
    
      /**
       * ORDERS
       */
    
      require_once '_shared.php';
    
      class Orders extends Methods {
    
        public function get_orders () {
          return $_SESSION["orders_data"];
        }
    
        public function create_new_order () {
          array_push($_SESSION["orders_data"], json_decode($this->data, true));
          return $_SESSION["orders_data"];
        }
    
      }
    
    ?>

    customers.php

    <?php
    
    /**
     * CUSTOMER ORDERS
     */
    
    require_once '_shared.php';
    
    class Customer extends Methods {
      
      public function get_all_names () {
        return array_map(function ($customer) {
          return $customer["name"];
        }, $_SESSION["customers_data"]);
      }
    
      public function get_customers () {
        return $_SESSION["customers_data"];
      }
    
      public function get_single_customer () {
        foreach($_SESSION["customers_data"] as $customer) {
          if($customer["index"] == $this->prop) {
            return $customer;
          }
        }
    
        return null;
      }
    
      public function get_all_emails () {
        return array_map(function ($customer) {
          return $customer["email"];
        }, $_SESSION["customers_data"]);
      }
    
      public function create_new_customer () {
        array_push($_SESSION["customers_data"], json_decode($this->data, true));
        return $_SESSION["customers_data"];
      }
    }
    
    ?>

    Customers.php file is a little more extended – it has more methods just to show you how easy you can now extend your simple PHP REST API.

  4. Now you must realize – if our API would grow and grow, it would be hard to maintain all cases inside index.php file just by if/switch statements. It would look very messy. So we will build now important functionality – the JSON definition of all used methods (both GET/PUT/POST and internal names of methods and end points). This definition is a matrix where values are name of methods triggered for orders and customers. This functionality will be used in index.php file.For that purpose we create switcher.phpfile with following code:
    switcher.php

    <?php
    
    class Switcher {
      public $methods_JSON = '{
        "customers": {
          "GET": {
            "default": "get_customers",
            "customer": "get_single_customer",
            "emails": "get_all_emails",
            "names": "get_all_names"
          },
          "PUT": {
            "default": "create_new_customer"
          }
        },
        "orders": {
          "GET": {
            "default": "get_orders"
          },
          "PUT": {
            "default": "create_new_order"
          }
        }
      }';
    
      public $methods = [];
    
      public function __construct () {
        $this->methods = json_decode($this->methods_JSON, true);
      }
    
      public function get_function ($method, $http_method, $end_point) {
        if(!$end_point) {
          $end_point = "default";
        }
    
        $method_item =      $this->find_in_array_by_key($this->methods, $method);
        $http_method_item = $this->find_in_array_by_key($method_item, $http_method);
        $function =         $this->find_in_array_by_key($http_method_item, $end_point);
    
        return $function;
      }
    
      private function find_in_array_by_key ($arr, $key) {
        $results = array_filter($arr, function ($item) use ($key) {
          return $item === $key;
        }, ARRAY_FILTER_USE_KEY);
    
        if(isset($results) && !empty($results) && count($results) === 1) {
          return $results[$key];
        } else {
          return [];
        }
      }
    }
    
    ?>

     

  5. Let’s move to index.php. It has changed a little, but I will put here whole code of that file:
    index.php

    <?php
    
      require_once '_shared.php';
      require_once 'storage.php';
      require_once 'switcher.php';
      require_once 'customers.php';
      require_once 'orders.php';
    
      /**
       * RUN API HERE
       */
    
      set_faked_data();
      runApi();
    
      function decodePath() {
        if(!empty($_GET['path'])) {
          $path_parts = $_GET['path'];
        } else {
          $path_parts = false;
        }
        
        if(empty($path_parts)) {
          return false;
        }
    
        if(strrpos($path_parts, '/') === false) {
          $path_parts .= '/';
        }
    
        $path_parts = explode('/', $path_parts);
        
        if(empty($path_parts[count($path_parts) - 1])) {
          array_pop($path_parts);
        }
        
        return $path_parts;
      }
    
      function runApi () {
        /*
        * 1st param: method
        * 2nd param: detailed action
        * 3rd param: props
        */
        
        $data = file_get_contents("php://input");
        $path = decodePath();
        
        $http_method = $_SERVER["REQUEST_METHOD"];
        $method = isset($path[0]) && !empty($path[0]) ? $path[0] : null;
        $end_point = isset($path[1]) && !empty($path[1]) ? $path[1] : null;
        $prop = isset($path[2]) && !empty($path[2]) ? $path[2] : null;
        $data = sizeof($data) ? $data : null;
        
        $sheduler = new Switcher();
        $func_name = $sheduler->get_function($method, $http_method, $end_point);
        
        if(!$func_name) {
          send_results("Method not found", 404);
          return;
        }
        
        switch ($method) {
          case "customers":
            
            $customer_functions = new Customer($prop, $data);
            $func_res = call_user_func( array($customer_functions, $func_name) );
            send_results($func_res);
    
            break;
          case "orders":
            $orders_functions = new Orders($prop, $data);
            $func_res = call_user_func( array($orders_functions, $func_name) );
            send_results($func_res);
    
            break;
          default:
            send_results("", 404);
        }
    
      }
    
    
    ?>

    The difference is on the very top of the file – there are included new files of course.

    Next, in runApi() function there is created a new instance of a Switcher class – which will give us instant mapping of the end point’s function by used HTTP method, URL path method and end point.
    It will map it and save in $func_name variable.

    Next, we have switch method which is used here to identify the main URL path method and create instance of right ORDERS or CUSTOMERS class.

    You can now extend you API with a new methods very easy – just extend JSON API definition in switcher.php file and in index.php file in switch method – if you introduce a new main method.

  6. You can type now in your browser URLs like:
    http://localhost:80/php-api-2/customers
    http://localhost:80/php-api-2/customers/emails
    http://localhost:80/php-api-2/customers/names
    http://localhost:80/php-api-2/customers/customer/1
    http://localhost:80/php-api-2/orders

    And you will get results like on screen shots below:

    simple REST API in PHP

    simple REST API in PHP

  7.  If you want to test PUT request, do it here for customers via URL: http://localhost:80/php-api-2/customers:

    simple REST API in PHP
    simple REST API in PHP