PHP REST API Skeleton
Create a /src directory, and a composer.json file in the top directory with one dependency: the DotEnv library, which allows storing secured pieces of information in .env file.
"require": "vlucas/phpdotenv": "^5.3" >, "autoload": "psr-4": "Src\\": "src/" > > >
A PSR-4 autoloader will automatically look for PHP classes in the /src directory.
It will create a /vendor directory, and the DotEnv dependency will be installed (autoloader will load classes from /src with no include() calls).
Create a .gitignore file for your project with two lines in it, so the /vendor directory and local .env file will be ignored:
Next, create a .env.example file for Secret variables:
DB_HOST=localhost DB_PORT=3306 DB_DATABASE= DB_USERNAME= DB_PASSWORD=
and a .env file where you’ll fill in your actual details later (it will be ignored by Git so it won’t end up in your repository).
Create a start.php file which loads the environment variables.
require 'vendor/autoload.php'; $dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->safeLoad(); echo $_ENV['DB_HOST']; // test code: // it will output: localhost // when you run $ php start.php
Configure the Database for your PHP REST API
We will use MySQL to power our simple API.
Create a new database and user for your app:
mysql -u root -p CREATE DATABASE blog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'rest_api_user'@'localhost' identified by 'rest_api_password'; GRANT ALL on blog.* to 'rest_api_user'@'localhost'; quit
The REST API will contain posts for our Blog Application, with the following fields: id , title , body , author , author_picture , created_at . It allows users to post their blog on our Blog application.
Create the database table in MySQL.
mysql -u rest_api_user -p; // Enter your password use blog; CREATE TABLE `post` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL, `body` text NOT NULL, `author` varchar(255), `author_picture` varchar(255), `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) );
Add the database connection variables to your .env file:
DB_HOST=localhost DB_PORT=3306 DB_DATABASE=blog DB_USERNAME=rest_api_user DB_PASSWORD=rest_api_password
Create a class to hold the database connections and add the initialization of the connection to the start.php file.
namespace Src; class Database private $dbConnection = null; public function __construct() $host = $_ENV['DB_HOST']; $port = $_ENV['DB_PORT']; $db = $_ENV['DB_DATABASE']; $user = $_ENV['DB_USERNAME']; $pass = $_ENV['DB_PASSWORD']; try $this->dbConnection = new \PDO( "mysql:host=$host;port=$port;dbname=$db", $user, $pass ); > catch (\PDOException $e) exit($e->getMessage()); > > public function connet() return $this->dbConnection; > >
require 'vendor/autoload.php'; use Src\Database; $dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->safeLoad(); $dbConnection = (new Database())->connet();
Add a Class for the Post Table and Implement PHP REST API
There are many ways to interact with the database in an object-oriented context, but, let us go with a simple method where you will implement methods to return all posts, return a specific post and add/update/delete a post.
Also, the API endpoints which will be handled by our frontend api/index.php .
REST API with the following endpoints:
Your APIs
API | CRUD | Description |
---|---|---|
GET /posts | READ | Get all the Posts from post table |
GET /post/ | READ | Get a single Post from post table |
POST /post | CREATE | Create a Post and insert into post table |
PUT /post/ | UPDATE | Update the Post in post table |
DELETE /post/ | DELETE | Delete a Post from post table |
require "../start.php"; use Src\Post; header("Access-Control-Allow-Origin: *"); header("Content-Type: application/json; charset=UTF-8"); header("Access-Control-Allow-Methods: OPTIONS,GET,POST,PUT,DELETE"); header("Access-Control-Max-Age: 3600"); header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $uri = explode( '/', $uri ); // endpoints starting with `/post` or `/posts` for GET shows all posts // everything else results in a 404 Not Found if ($uri[1] !== 'post') if($uri[1] !== 'posts') header("HTTP/1.1 404 Not Found"); exit(); > > // endpoints starting with `/posts` for POST/PUT/DELETE results in a 404 Not Found if ($uri[1] == 'posts' and isset($uri[2])) header("HTTP/1.1 404 Not Found"); exit(); > // the post id is, of course, optional and must be a number $postId = null; if (isset($uri[2])) $postId = (int) $uri[2]; > $requestMethod = $_SERVER["REQUEST_METHOD"]; // pass the request method and post ID to the Post and process the HTTP request: $controller = new Post($dbConnection, $requestMethod, $postId); $controller->processRequest();
namespace Src; class Post private $db; private $requestMethod; private $postId; public function __construct($db, $requestMethod, $postId) $this->db = $db; $this->requestMethod = $requestMethod; $this->postId = $postId; > public function processRequest() switch ($this->requestMethod) case 'GET': if ($this->postId) $response = $this->getPost($this->postId); > else $response = $this->getAllPosts(); >; break; case 'POST': $response = $this->createPost(); break; case 'PUT': $response = $this->updatePost($this->postId); break; case 'DELETE': $response = $this->deletePost($this->postId); break; default: $response = $this->notFoundResponse(); break; > header($response['status_code_header']); if ($response['body']) echo $response['body']; > > private function getAllPosts() $query = " SELECT id, title, body, author, author_picture, created_at FROM post; "; try $statement = $this->db->query($query); $result = $statement->fetchAll(\PDO::FETCH_ASSOC); > catch (\PDOException $e) exit($e->getMessage()); > $response['status_code_header'] = 'HTTP/1.1 200 OK'; $response['body'] = json_encode($result); return $response; > private function getPost($id) $result = $this->find($id); if (! $result) return $this->notFoundResponse(); > $response['status_code_header'] = 'HTTP/1.1 200 OK'; $response['body'] = json_encode($result); return $response; > private function createPost() $input = (array) json_decode(file_get_contents('php://input'), TRUE); if (! $this->validatePost($input)) return $this->unprocessableEntityResponse(); > $query = " INSERT INTO post (title, body, author, author_picture) VALUES (:title, :body, :author, :author_picture); "; try $statement = $this->db->prepare($query); $statement->execute(array( 'title' => $input['title'], 'body' => $input['body'], 'author' => $input['author'], 'author_picture' => 'https://secure.gravatar.com/avatar/'.md5(strtolower($input['author'])).'.png?s=200', )); $statement->rowCount(); > catch (\PDOException $e) exit($e->getMessage()); > $response['status_code_header'] = 'HTTP/1.1 201 Created'; $response['body'] = json_encode(array('message' => 'Post Created')); return $response; > private function updatePost($id) $result = $this->find($id); if (! $result) return $this->notFoundResponse(); > $input = (array) json_decode(file_get_contents('php://input'), TRUE); if (! $this->validatePost($input)) return $this->unprocessableEntityResponse(); > $statement = " UPDATE post SET title = :title, body = :body, author = :author, author_picture = :author_picture WHERE "; try $statement = $this->db->prepare($statement); $statement->execute(array( 'id' => (int) $id, 'title' => $input['title'], 'body' => $input['body'], 'author' => $input['author'], 'author_picture' => 'https://secure.gravatar.com/avatar/'.md5($input['author']).'.png?s=200', )); $statement->rowCount(); > catch (\PDOException $e) exit($e->getMessage()); > $response['status_code_header'] = 'HTTP/1.1 200 OK'; $response['body'] = json_encode(array('message' => 'Post Updated!')); return $response; > private function deletePost($id) $result = $this->find($id); if (! $result) return $this->notFoundResponse(); > $query = " DELETE FROM post WHERE "; try $statement = $this->db->prepare($query); $statement->execute(array('id' => $id)); $statement->rowCount(); > catch (\PDOException $e) exit($e->getMessage()); > $response['status_code_header'] = 'HTTP/1.1 200 OK'; $response['body'] = json_encode(array('message' => 'Post Deleted!')); return $response; > public function find($id) $query = " SELECT id, title, body, author, author_picture, created_at FROM post WHERE "; try $statement = $this->db->prepare($query); $statement->execute(array('id' => $id)); $result = $statement->fetch(\PDO::FETCH_ASSOC); return $result; > catch (\PDOException $e) exit($e->getMessage()); > > private function validatePost($input) if (! isset($input['title'])) return false; > if (! isset($input['body'])) return false; > return true; > private function unprocessableEntityResponse() $response['status_code_header'] = 'HTTP/1.1 422 Unprocessable Entity'; $response['body'] = json_encode([ 'error' => 'Invalid input' ]); return $response; > private function notFoundResponse() $response['status_code_header'] = 'HTTP/1.1 404 Not Found'; $response['body'] = null; return $response; > >
Let’s start the PHP Server and test your APIs with a tool like Postman.
php -S localhost:8000 -t api