TubeSum ← Transcribe a video

Create a PHP REST API : Write a RESTful API from Scratch using Plain, Object-Oriented PHP and MySQL

Transcribed Jun 16, 2026 Watch on YouTube ↗
Intermediate 12 min read For: PHP developers with basic knowledge of OOP and MySQL who want to learn how to build a RESTful API without frameworks.
124.2K
Views
2.7K
Likes
226
Comments
9
Dislikes
2.4%
📈 Moderate

AI Summary

This video tutorial teaches how to build a complete RESTful API using plain object-oriented PHP and MySQL. It covers setting up endpoints, routing, database integration, and handling CRUD operations (Create, Read, Update, Delete) with proper HTTP methods and status codes. The code is free to download, and the tutorial uses tools like HTTPie for testing.

[0:42]
Capturing and Parsing the URL

Use `$_SERVER['REQUEST_URI']` to get the URL path, then parse it with `explode()` to extract segments like 'products' and an optional ID.

[2:09]
URL Rewriting with .htaccess

Create an `.htaccess` file with `RewriteEngine On` and a rule to route all requests to `index.php`.

[5:19]
Creating the Controller Class

Create a `ProductController` class with a `processRequest` method that takes the HTTP method and ID, then delegates to `processCollectionRequest` or `processResourceRequest`.

[6:39]
Autoloading Classes

Use `spl_autoload_register()` to automatically load class files from the 'source' folder.

[10:20]
Setting JSON Content Type

Set the Content-Type header to 'application/json; charset=utf-8' to return JSON responses.

[11:59]
Database Connection Class

Create a `Database` class that uses PDO to connect to MySQL, with configurable host, dbname, username, and password.

[14:48]
Custom Exception Handler

Implement a custom exception handler using `set_exception_handler()` to return errors as JSON with a 500 status code.

[16:51]
Product Gateway for Database Operations

Create a `ProductGateway` class with methods like `getAll`, `get`, `create`, `update`, and `delete` to interact with the product table.

[20:26]
PDO Configuration for Native Types

Configure PDO attributes `ATTR_EMULATE_PREPARES` and `ATTR_STRINGIFY_FETCHES` to false to preserve integer and boolean types.

[22:46]
Handling POST Requests and Validation

For POST requests, read JSON data from `php://input`, decode it, and validate required fields like 'name'.

[28:12]
Error Handler for PHP Errors

Use `set_error_handler()` to convert PHP errors into exceptions, ensuring they are also returned as JSON.

[37:07]
Updating Resources with PATCH

For PATCH requests, update only the provided fields, using existing values for missing ones.

[41:12]
Deleting Resources

For DELETE requests, remove the record and return the number of affected rows.

[42:43]
Handling Unsupported Methods

Return 405 Method Not Allowed with an Allow header for unsupported HTTP methods.

Clickbait Check

95% Legit

"The title accurately describes the video content: building a RESTful API from scratch with plain PHP and MySQL."

Mentioned in this Video

Tutorial Checklist

1 0:42 Create index.php and print $_SERVER['REQUEST_URI'] to see the URL path.
2 2:09 Create .htaccess file with RewriteEngine On and a rule to route all requests to index.php.
3 3:29 Use explode() to split the URL path into segments and check if the first segment is 'products'.
4 5:19 Create ProductController class with processRequest method that takes method and id arguments.
5 6:39 Set up autoloading with spl_autoload_register to load classes from the 'source' folder.
6 10:20 Set the Content-Type header to 'application/json; charset=utf-8'.
7 11:59 Create Database class with constructor for host, dbname, username, password, and getConnection method returning PDO.
8 15:10 Create ErrorHandler class with handleException method that outputs error details as JSON with 500 status.
9 16:51 Create ProductGateway class with getAll method that queries the product table and returns an array of rows.
10 20:26 Configure PDO attributes to disable emulate prepares and stringify fetches to preserve data types.
11 21:33 In ProductController, handle GET request to return all products as JSON.
12 21:39 Handle POST request by reading JSON from php://input, decoding it, and calling create method on gateway.
13 30:08 Add validation for required fields (e.g., name) and return 422 status for invalid data.
14 32:49 Add default case in switch to return 405 Method Not Allowed with Allow header.
15 33:53 Add get method to ProductGateway to fetch a single product by ID, and handle 404 if not found.
16 37:07 Handle PATCH request to update a product, using existing values for missing fields.
17 41:12 Handle DELETE request to remove a product and return the number of affected rows.

Study Flashcards (14)

How do you get the request URI in PHP?

easy Click to reveal answer

Use the `$_SERVER['REQUEST_URI']` superglobal.

0:42

How do you split the URL path into segments?

easy Click to reveal answer

Use the `explode()` function with '/' as the delimiter.

3:29

How do you set an HTTP status code in PHP?

easy Click to reveal answer

Use the `http_response_code()` function.

4:15

How do you implement autoloading for classes in PHP?

medium Click to reveal answer

Use `spl_autoload_register()` with a callback that requires the class file.

6:39

How do you set the response content type to JSON?

medium Click to reveal answer

Set the Content-Type header to 'application/json; charset=utf-8' using the `header()` function.

10:20

How do you get JSON data from a POST request?

medium Click to reveal answer

Use `file_get_contents('php://input')` to read the raw request body.

22:46

How do you decode JSON into an associative array?

easy Click to reveal answer

Use `json_decode($data, true)` to convert JSON string to an associative array.

23:03

How do you prevent PDO from converting integers and booleans to strings?

hard Click to reveal answer

Set `PDO::ATTR_EMULATE_PREPARES` and `PDO::ATTR_STRINGIFY_FETCHES` to false.

20:26

How do you create a custom exception handler that returns JSON?

medium Click to reveal answer

Use `set_exception_handler()` with a callback that outputs JSON.

14:48

How do you handle PHP errors as exceptions?

hard Click to reveal answer

Use `set_error_handler()` to convert PHP errors into exceptions.

28:12

How do you validate that a value is an integer in PHP?

medium Click to reveal answer

Use the `filter_var()` function with `FILTER_VALIDATE_INT`.

30:47

What HTTP status code should you return for validation errors?

medium Click to reveal answer

Return a 422 Unprocessable Entity status code.

31:44

How do you respond to an unsupported HTTP method?

medium Click to reveal answer

Return a 405 Method Not Allowed status code with an Allow header.

32:49

How do you get the ID of the last inserted record?

easy Click to reveal answer

Use `$pdo->lastInsertId()`.

25:40

💡 Key Takeaways

🔧

URL Rewriting with .htaccess

Shows how to route all requests to a single entry point, a fundamental pattern for RESTful APIs.

2:09
🔧

Autoloading Classes

Demonstrates a simple autoloader using spl_autoload_register, avoiding manual requires.

6:39
🔧

Custom Exception Handler for JSON

Ensures all errors are returned as JSON, maintaining API consistency.

14:48
🔧

PDO Configuration for Native Types

Prevents PDO from converting integers and booleans to strings, preserving data types in JSON.

20:26
⚖️

Dependency Injection in Controller

Injects the ProductGateway into the controller, promoting loose coupling and testability.

18:44

✂️ Creator Tools: Viral Hooks

AI-generated clip ideas for Shorts based on the transcript

No viral clips found for this video, or they are still being generated.

[00:00] in this video you'll learn how to create a 

[00:07] php and mysql all the code developed in this video 

[00:15] description to make api requests you'll need 

[00:26] in this video i'll be using httpi on the command 

[00:35] we'll create a new file called index.php in 

[00:42] and for now let's just print out the value of the 

[00:51] let's see what response we get from that page on 

[00:59] to that page and we see the response status code 

[01:06] body in the response body we see the output of 

[01:13] path part of the url index.php if we make that 

[01:23] index.php is the default the request still works 

[01:32] so this server variable contains the part 

[01:39] is going to be restful however we want a specific 

[01:47] urls one for a collection of resources and one for 

[01:56] the result of calling the endpoint 

[02:00] and the request method used so in order to have 

[02:09] as i'm using apache let's add a new file called 

[02:16] settings in here we'll turn the rewrite engine on 

[02:24] that matches any url and we'll 

[02:30] basically this says that any url will now call 

[02:37] rules are only valid for apache and compatible 

[02:43] very simple for the purposes of this video but if 

[02:49] let me know in the comments let's give that a try 

[02:57] script is executed and we get the path part of 

[03:06] default request method is get so if we're making 

[03:13] also the http protocol is the default so 

[03:20] we have access to this part of the url from our 

[03:29] so instead of printing this out we'll 

[03:34] using the forward slash character we'll 

[03:42] if we call the slash product endpoint then 

[03:48] with an index of one containing the string 

[03:54] then this is placed in an additional array element 

[04:02] url is something else we'll respond with a 404 

[04:10] so instead of printing this out if 

[04:15] we'll call the http response code function 

[04:21] exit the script as for the id this will be 

[04:29] we'll default it to null for now let's 

[04:36] if we make a request to an end point that isn't 

[04:44] if we make a request to the collection endpoint  

[04:47] we get a response with a status code of 200 which 

[04:54] is null if we add the id to 

[05:01] note that for the purposes of this 

[05:06] with a simple rewrite rule and passing the 

[05:12] project you'd probably use a third-party router 

[05:19] next let's add a controller class to control the 

[05:26] to store the class files called source then a 

[05:34] in here we'll add the php opening tag and the 

[05:43] to this called process request the response 

[05:49] resource id or not so let's add a string argument 

[05:57] to keep it simple we'll just echo out the response 

[06:03] as void as we just saw when we made a request the 

[06:10] nullable inside this method for now let's just 

[06:19] back in the index script as we're using type 

[06:25] the top of the script then in order to use the 

[06:32] that it's in instead of explicitly doing this 

[06:39] let's add an auto loader to load class files 

[06:45] auto load register function passing in 

[06:50] whenever a previously unused class is referenced 

[06:58] in the body of the function we'll require that 

[07:04] to reference the directory of the current file the 

[07:11] the name of the class you are trying to use so as 

[07:18] add the dot php suffix and we'll get the right 

[07:25] composer's autoloader but again for the purposes 

[07:32] then at the end of the script now we can 

[07:37] so instead of printing out the id 

[07:41] of the product controller class then we 

[07:47] passing in the request method which we can 

[07:55] so now if we make a request to slash 

[08:00] automatically and we get the request method 

[08:07] if i add the id then we get that printed out too  

[08:12] if i change the request method for example 

[08:19] back in the controller now we can actually process 

[08:26] we're going to base the response on the request 

[08:32] request that affects a single resource and if 

[08:38] resources instead of doing all the processing 

[08:44] each one of these a process resource request 

[08:50] id as arguments and a process collection request 

[08:59] then back in the process request 

[09:07] let's start by processing the collection requests

[09:12] a get request to slash products should return a 

[09:20] new product so in the process collection request 

[09:26] switch statement if it's get will return a list of 

[09:34] javascript object notation or json so let's start 

[09:42] element encoded as json then a break statement 

[09:50] to slash products then we see the array in the 

[09:57] type response header it's set to text html 

[10:05] we need to set this to the json content 

[10:13] response we send back from the api will be in 

[10:20] so just after we set up the auto loader we'll call 

[10:26] content type header to application json and also 

[10:36] so when we make the same request the 

[10:41] plus using httpi the response body now 

[10:49] now we have this setup we can return some 

[10:56] i'm going to create a database 

[11:01] this will create a database called productdb 

[11:08] this table contains various 

[11:12] to demonstrate how these are handled in an api 

[11:18] link to the source code that includes this sql 

[11:29] and there's the database and the product table

[11:34] while we're here let's just insert a 

[11:39] we don't need to provide values for the id 

[11:45] values for this will be assigned automatically 

[11:53] now we can connect to the database from 

[11:59] the database connection so we'll add a new 

[12:07] in here we'll add the php opening tag and the 

[12:14] we need details such as the hostname database name 

[12:21] when we create an object of this class using 

[12:27] the database host database name username and 

[12:34] port for example then you can include them here 

[12:41] automatically create private properties for each 

[12:47] to these properties next let's add a method called 

[12:55] in the body of this method let's create a variable 

[13:01] name using the values of the properties we 

[13:08] object passing in the dsn username and password 

[13:16] in the index page let's create an object 

[13:21] host name username and password i'm using the 

[13:28] this is fine when you're developing locally but 

[13:34] have its own username and password also to keep 

[13:40] here but in reality what you'd probably do is 

[13:46] to test the database code we just created let's 

[13:54] if we make a get request to slash products then 

[13:59] json test data this means that the class has 

[14:05] the database successfully however let's see what 

[14:12] details wrong for example an incorrect password 

[14:21] this is as expected however by default 

[14:26] html we always want to return json 

[14:34] to fix this we could put a try catch block around 

[14:40] error details as json in the catch block instead 

[14:48] will output any unhandled exception as json we can 

[14:56] this takes a callback function that will be 

[15:02] the single argument to this function is an object 

[15:10] the source folder called errorhandler.php 

[15:17] and the class definition then let's add a method 

[15:24] of type throwball that represents the exception 

[15:31] the body of the method we'll output some json that 

[15:38] can get the error code the error message the file 

[15:46] in addition to outputting the exception details 

[15:52] status code to 500 indicating that there is an 

[16:00] generic exception handler in the index file we 

[16:07] in a string that identifies the handle exception 

[16:14] we do this as early as possible in this script 

[16:19] but so that the class is loaded automatically we 

[16:26] let's make the api request again and now 

[16:32] along with the 500 internal 

[16:38] now we've tested the error handler we can change 

[16:45] use this database connection to make a query 

[16:51] class we'll start by adding a new file in 

[16:58] this class uses the table gateway pattern 

[17:04] product table in the database we'll add the php 

[17:11] in this class depend on a database connection 

[17:17] in using the constructor all the methods in this 

[17:23] so instead of using constructor property 

[17:29] let's add a private property to the class to 

[17:35] body of the constructor method we'll call the get 

[17:40] this in the property then let's add a method 

[17:47] we'll add a new public method called get all and 

[17:54] method we'll add a variable containing the sql 

[18:00] we'll keep it simple with no order by clause or 

[18:07] all of the records then we'll call the query 

[18:15] and assigning the returned pdo statement object 

[18:23] so let's initialize a variable with an empty array 

[18:29] turn as an associative array inside the loop 

[18:36] in this method return the array of row data now we 

[18:44] we'll do here in the product controller class 

[18:50] product gateway class in here we'll pass one in as 

[18:57] with a product gateway object argument and 

[19:03] so that the value of this argument will 

[19:07] same name then in the process collection request 

[19:16] we'll call the get all method on the gateway 

[19:22] script we just need to stitch all this together 

[19:28] in here we'll create a new product gateway object 

[19:35] can pass this gateway object in as an argument 

[19:43] let's give that a try now if we make the same 

[19:48] two records from the database that we inserted 

[19:54] record are encoded as strings even though in the 

[20:01] and the is available column is boolean this is 

[20:10] if the pdo stringify fetch's attribute is true  

[20:13] then all values will be converted to 

[20:19] we also need to set the emulate prepares 

[20:26] so in the database class when we create a 

[20:32] that's an array and we'll set both the emulate 

[20:40] now if we make the api request again the 

[20:46] note however that the boolean value is also 

[20:52] as internally the database server stores boolean 

[21:00] you want these to be boolean literals in the 

[21:07] so in the product gateway class inside the 

[21:12] database before we append the row to the array 

[21:20] now when we make the api request the is 

[21:26] true or false so when we make a get request 

[21:33] a list of all the products next let's add the 

[21:39] do by making a post request to the same url so 

[21:48] collection request method we'll add a new case to 

[21:54] is post first we need to get the data from the 

[22:02] that you would submit from a browser the data from 

[22:08] so let's assign that array to a variable and 

[22:15] request to the collection endpoint with some data 

[22:22] by default httpi sends data in the request encoded 

[22:31] data we'll require that data sent to the api is 

[22:40] we get json data from the request 

[22:46] we do this by using the file get contents function 

[22:55] now if we make the same request we get a string 

[23:03] using the json decode function passing in true as 

[23:11] if we make the request again now we have an 

[23:16] request if we add another item of data this adds 

[23:25] by default values are encoded as strings to send 

[23:33] we need to add a colon before the equal sign now 

[23:40] note what happens though if we make a post 

[23:47] we get null to make the api more robust 

[23:54] check that this value isn't null the json decode 

[24:01] or if there isn't any data we could check for 

[24:07] do it is to just cast this value to an array 

[24:14] happens if it's null then it will be converted to 

[24:22] again now we get an empty array instead of null 

[24:30] we can insert a new record into the database 

[24:37] let's add a new public method called create with 

[24:44] we'll add a string containing the sql to insert 

[24:50] the name size and is available columns with 

[24:58] a prepared statement for this so we'll call 

[25:03] passing in the sql then we can bind the values to 

[25:14] then the size which will default to zero if it 

[25:21] finally the is available column which will 

[25:27] just in case the value was supplied as another 

[25:34] then we'll execute the statement and we'll 

[25:40] which we can get by calling the last 

[25:46] a string so we'll add the string return 

[25:54] then back in the controller instead of 

[25:59] we can call the create method on the gateway 

[26:06] returns the id of the newly created record so 

[26:13] to this request we'll output some json with 

[26:18] and its id finally we'll add a break 

[26:26] let's give that a try if we make a post request to 

[26:32] name we get a reply saying the product with an 

[26:41] a product with an id of 4 and if we include 

[26:50] in the database we can see the three new 

[26:56] where we didn't specify values for the size and 

[27:02] defaulted note that in the output when we 

[27:09] 200. this is the default status code if we don't 

[27:17] status code to use is 2 0 201 so let's set 

[27:27] now if we make a successful request to create a 

[27:34] watch what happens though if we make a request 

[27:41] the first tells us we have an undefined array 

[27:47] comes from trying to insert a record with 

[27:53] that the first error isn't encoded as json even 

[28:00] what's actually happening here is that a 

[28:06] by the custom exception handler to catch the 

[28:12] using the set error handler function the callback 

[28:18] the error number message and so on so first in the 

[28:26] called handle error which fits this signature 

[28:36] inside the method instead of repeating 

[28:40] we can just throw a new exception 

[28:45] this exception class was designed to 

[28:50] constructor takes various different arguments 

[28:57] so in the body of the handle error method we'll 

[29:03] message zero for the exception code as we don't 

[29:10] and the line number in that file then in the 

[29:17] we'll call the set error handler function 

[29:25] now if we make a post request without 

[29:31] we also no longer get the other exception as 

[29:37] which is as expected now we can look at 

[29:43] we're getting an undefined array key error 

[29:51] on that line we're trying to use 

[29:55] this is because we didn't send any data with 

[30:01] data to the database we need to validate it 

[30:08] let's add a new private method called get 

[30:14] which is the array of data to validate 

[30:20] in the body of the method first we'll initialize 

[30:28] we'll keep this simple and just 

[30:33] if it is we'll append an error 

[30:39] next we'll validate the size this is optional so 

[30:47] if so we'll use the filter var function with 

[30:52] it's an integer if it's not this function will 

[31:01] the reason we check it's specifically boolean 

[31:06] function call is that it could be zero which 

[31:12] boolean false if it's not valid we'll append 

[31:20] at the end of the method we'll return the array 

[31:27] before we call the create method we'll call the 

[31:32] the request and assigning the return value 

[31:40] then we'll output the errors as json in 

[31:44] case block we'll also return an http status 

[31:55] let's give that a try now if we make a 

[32:00] in the body of the response and a 422 status code  

[32:05] if we add an invalid size to the data 

[32:12] if we send valid data then the record is 

[32:19] so now we can make a get request to this 

[32:25] and a post request to create a new one there 

[32:32] example delete if we make a delete request to this 

[32:40] status code we should respond differently if this 

[32:49] add a default block to the switch statement and 

[32:56] is method not allowed when responding with this 

[33:03] the methods that are allowed in an allow header 

[33:09] with an allow header specifying that get and post 

[33:17] now if we try an invalid method we get the 

[33:23] header is included if we try a valid method such 

[33:33] that completes the processing for the collection 

[33:39] product id is included in the endpoint the 

[33:46] is to check that a record already exists in 

[33:53] gateway class let's add a new public method 

[34:01] as above we'll use a prepared statement for this 

[34:07] from the product table using a placeholder 

[34:13] passing in the sql and bind the value 

[34:21] next we'll execute it and fetch the record as 

[34:28] we'll return the data the fetch method will 

[34:34] are false otherwise so we'll add the appropriate 

[34:43] then in the controller in the process resource 

[34:49] gateway property passing in the id then for now 

[35:00] let's give that a try if we make a get request 

[35:07] then we get the product record in 

[35:12] note however that the is available column 

[35:19] in the get all method in the product gateway 

[35:25] boolean value when we retrieve the data 

[35:32] once we fetch the data if it isn't 

[35:42] now if we make the same request again 

[35:47] this request worked because 

[35:51] exists in the database if we make a request 

[36:00] back in the product controller 

[36:04] and if the product wasn't found 

[36:09] a suitable message in the response body and we'll 

[36:16] now if we make the same request 

[36:23] next we can process the request 

[36:28] as before we'll do this with a switch 

[36:34] if the method is get we'll do what we're already 

[36:41] then we'll break from the case block we 

[36:46] as this is the default but you can if you want to 

[36:54] a product that exists we get a 200 response 

[37:02] next we'll process a request 

[37:07] we'll do this with a patch request so let's add 

[37:15] most of this will be similar to what we're doing 

[37:21] copy this whole case block and paste it in the 

[37:28] we get the data from the request and validate 

[37:35] on the gateway object we need a different method 

[37:42] public method to the product gateway class called 

[37:50] and the new details from the request data and 

[37:56] of rows in the database that were updated in here 

[38:03] the record with a where clause for the id we'll 

[38:13] then we can bind values to the placeholders 

[38:19] passed in the request we'll use that otherwise 

[38:25] hasn't changed the database will detect this and 

[38:33] and is available columns then we'll bind the id 

[38:42] and we'll execute the statement we'll 

[38:47] return value from this method then back in the 

[38:55] we call the update method we just added passing 

[39:02] this returns the number of rows affected so we'll 

[39:08] accordingly for the response we'll use the 

[39:14] to the http response code function as this 

[39:21] we'll include the id in the message and 

[39:28] let's give that a try we'll send a patch request 

[39:36] we get a successful response telling us one row 

[39:42] endpoint then we see the name value has indeed 

[39:49] again then no rows were affected as no values 

[39:59] and the validation works as before however if 

[40:07] then we get a validation error saying that 

[40:13] when we're creating a new record so we need 

[40:20] in the get validation errors method we'll only 

[40:26] if we're creating a new record so let's add a new 

[40:33] a new record or not which will default to true 

[40:41] we'll check if this variable is true then 

[40:48] we can pass in false as the second argument 

[40:55] let's give that a try if we make 

[40:59] just passing in a value for the size now we 

[41:04] updated and if we view that record we can 

[41:12] the final operation we want to carry out on 

[41:18] in the process resource request method by 

[41:25] then in the product gateway class we'll add a 

[41:31] id as a string and returning an integer in the 

[41:38] a record from the product table identified 

[41:45] the id value to the placeholder and execute it 

[41:55] back in the controller we'll call this method 

[42:01] assigning the return value to a variable for 

[42:07] the number of rows that were affected and 

[42:14] let's give that a try if we send a 

[42:18] an id of one then we get a result saying the 

[42:26] if we then try to read that resource by sending 

[42:31] get a 404 response saying the product wasn't 

[42:38] one final thing we need to do in here is 

[42:43] of the above methods as we did below so let's add 

[42:52] and send an allow header with the 

[42:59] so now if we send a post request for example 

[43:05] of 2 we get the 405 method not allowed response 

[43:14] to create a restful api using plain object 

[43:23] to list all resources to create one to show 

[43:35] there's a link to all the code shown 

[43:40] along with links to sites shown 

[43:45] please don't forget to like comment and 

⚡ Saved you time reading this? Transcribe any YouTube video for free — no signup needed.