Implementing Custom Field Selection in a REST API Using PHP and MySQL on Ubuntu 20.04

Offering users the ability to select specific fields when requesting data from your API is a common and practical feature. If your REST API delivers product data—perhaps with fields like product_id, product_name, retail_price, discount, category_name, reviews, ratings, and related_products—most clients will only need a subset of this data for their application UI, such as on mobile or desktop platforms.

This is where selective field responses become useful. For example, if you use an endpoint like http://localhost/products.php that returns JSON, users could refine the response by appending a fields parameter, such as http://localhost/products.php?fields=product_name,category_name,retail_price. Your backend logic would interpret this request, verify the requested fields against a predefined allow list, and return only those, thereby minimizing bandwidth use and server load.

This tutorial demonstrates how to set up that functionality in a REST API using PHP and MySQL on Ubuntu 20.04.

Requirements

Before getting started, ensure the following are available:

  • An Ubuntu 20.04 server
  • A user account with sudo privileges
  • A LAMP stack installed (with either MySQL or MariaDB)

Creating the Database

Start by connecting to your server and accessing MySQL with the command below:

After entering the MySQL root password, create a new database named variety_store:


mysql> CREATE DATABASE variety_store;

Next, add a dedicated user for the database. Replace EXAMPLE_PASSWORD with a secure password of your choice:


mysql> CREATE USER ‘variety_store_user’@’localhost’ IDENTIFIED WITH mysql_native_password BY ‘EXAMPLE_PASSWORD’;
mysql> GRANT ALL PRIVILEGES ON variety_store.* TO ‘variety_store_user’@’localhost’;
mysql> FLUSH PRIVILEGES;

If you’re using MariaDB, modify the grant command as follows:


MariaDB> GRANT ALL PRIVILEGES on variety_store.* TO ‘variety_store_user’@’localhost’ identified by ‘EXAMPLE_PASSWORD’;

Switch to the variety_store database to begin defining your schema:

Defining Tables and Inserting Data

Creating the Product Categories Table

Define a table to hold product categories:


mysql> CREATE TABLE products_categories (
category_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
category_name VARCHAR(50)
) ENGINE = InnoDB;

Add a few sample categories to the table:


mysql> INSERT INTO products_categories (category_name) VALUES (‘ELECTRONICS’);
mysql> INSERT INTO products_categories (category_name) VALUES (‘CLOTHING’);
mysql> INSERT INTO products_categories (category_name) VALUES (‘SHOES’);

Verify that the data was inserted correctly:


mysql> SELECT
category_id,
category_name
FROM products_categories;

The output should resemble the following:

+-------------+---------------+
| category_id | category_name |
+-------------+---------------+
|           1 | ELECTRONICS   |
|           2 | CLOTHING      |
|           3 | SHOES         |
+-------------+---------------+
3 rows in set (0.00 sec)

Creating the Products Table

Now define the main products table, which includes information like pricing, category links, and discounts:


mysql> CREATE TABLE products (
product_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(50),
category_id INT,
retail_price DOUBLE,
discount DOUBLE
) ENGINE = InnoDB;

Insert a few product entries, matching them with their corresponding category IDs:


mysql> INSERT INTO products (product_name, category_id, retail_price, discount) VALUES (‘5G PHONE’, ‘1’, ‘420.25’, ‘15.00’);
mysql> INSERT INTO products (product_name, category_id, retail_price, discount) VALUES (‘LED TORCH’, ‘1’, ‘5.35’, ‘0.00’);
mysql> INSERT INTO products (product_name, category_id, retail_price, discount) VALUES (‘WOOLEN SOCKS’, ‘2’, ‘9.99’, ‘0.00’);
mysql> INSERT INTO products (product_name, category_id, retail_price, discount) VALUES (‘LONG SLEEVE POLO’, ‘2’, ‘18.25’, ‘4.25’);
mysql> INSERT INTO products (product_name, category_id, retail_price, discount) VALUES (‘SUEDE SHOE’, ‘3’, ‘33.40’, ‘3.40’);

Confirm the entries by running a join query to see products alongside their category names:


mysql> SELECT
product_id,
product_name,
products.category_id,
category_name,
retail_price,
discount
FROM products
LEFT JOIN products_categories
ON products.category_id = products_categories.category_id;

The expected output should look like this:

+------------+------------------+-------------+---------------+--------------+----------+
| product_id | product_name     | category_id | category_name | retail_price | discount |
+------------+------------------+-------------+---------------+--------------+----------+
|          1 | 5G PHONE         |           1 | ELECTRONICS   |       420.25 |       15 |
|          2 | LED TORCH        |           1 | ELECTRONICS   |         5.35 |        0 |
|          3 | WOOLEN SOCKS     |           2 | CLOTHING      |         9.99 |        0 |
|          4 | LONG SLEEVE POLO |           2 | CLOTHING      |        18.25 |     4.25 |
|          5 | SUEDE SHOE       |           3 | SHOES         |         33.4 |      3.4 |
+------------+------------------+-------------+---------------+--------------+----------+
5 rows in set (0.00 sec)

To exit the MySQL prompt, run:

With your database schema and sample data now prepared, you’re ready to proceed with creating the PHP script that connects to this database and returns the data in JSON format.

Building the PHP Resource File

Next, create the products.php file under your web server’s root directory using nano:


$ sudo nano /var/www/html/products.php

Start the file with a <?php tag, then set the response type to JSON. Inside a try block, define the database name and access credentials you configured earlier.


setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Set up a list of default fields to return if the client request doesn’t specify any particular ones. Use explode and array_map('trim') to split the fields into a clean array.


$default_fields = ‘product_id,
product_name,
category_id,
category_name,
retail_price,
discount,
sale_price
‘;

$default_fields = array_map(‘trim’, explode(‘,’, $default_fields));

Check for the fields parameter in the URL. If present, parse it into the $user_fields array and remove any unauthorized fields using array_intersect. Otherwise, fall back to $default_fields.


if (isset($_GET[‘fields’])) {
$user_fields = array_map(‘trim’, explode(‘,’, $_GET[‘fields’]));
$columns = array_intersect($default_fields, $user_fields);
} else {
$columns = $default_fields;
}

Before executing the query, convert ambiguous or virtual field names. For example, category_id must be namespaced as products.category_id, and sale_price must be defined as a computed field.


for ($i = 0 ; $i < count($columns) ; $i++) {
if ($columns[$i] == ‘category_id’) {
$columns[$i] = ‘products.category_id’;
}
if ($columns[$i] == ‘sale_price’) {
$columns[$i] = ‘(retail_price – discount) as sale_price’;
}
}

$columns = implode(‘,’, $columns);

if (trim($columns) == ”) {
$error = [];
$error[‘message’] = “No valid fields are supplied”;
echo json_encode($error, JSON_PRETTY_PRINT) . “\n”;
exit();
}

<h4>Now build the SQL query using the sanitized column list. Join the products and products_categories tables, execute the query, and return the result in JSON format.</h4>


$sql = ‘select ‘
. $columns
. ‘ from products
left join products_categories
on products.category_id = products_categories.category_id
‘;

$stmt = $pdo->prepare($sql);
$stmt->execute();

$products = [];

while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$products[] = $row;
}

$response = [];
$response[‘data’] = $products;

echo json_encode($response, JSON_PRETTY_PRINT) . “\n”;

} catch (PDOException $e) {
$error = [];
$error[‘message’] = $e->getMessage();
echo json_encode($error, JSON_PRETTY_PRINT) . “\n”;
}

Save the file and close the editor. At this point, your /var/www/html/products.php file should look exactly like the example provided above.

Testing the API Endpoint

Begin by sending a request to the http://localhost/products.php endpoint without specifying any field filters:


$ curl http://localhost/products.php

You should see a response containing all default fields as defined in your PHP script. Note that the sale_price value is dynamically computed by subtracting discount from retail_price.

{
"data": [
    {
        "product_id": 1,
        "product_name": "5G PHONE",
        "category_id": 1,
        "category_name": "ELECTRONICS",
        "retail_price": 420.25,
        "discount": 15,
        "sale_price": 405.25
    },
    {
        "product_id": 2,
        "product_name": "LED TORCH",
        "category_id": 1,
        "category_name": "ELECTRONICS",
        "retail_price": 5.35,
        "discount": 0,
        "sale_price": 5.35
    },
    {
        "product_id": 3,
        "product_name": "WOOLEN SOCKS",
        "category_id": 2,
        "category_name": "CLOTHING",
        "retail_price": 9.99,
        "discount": 0,
        "sale_price": 9.99
    },
    {
        "product_id": 4,
        "product_name": "LONG SLEEVE POLO",
        "category_id": 2,
        "category_name": "CLOTHING",
        "retail_price": 18.25,
        "discount": 4.25,
        "sale_price": 14
    },
    {
        "product_id": 5,
        "product_name": "SUEDE SHOE",
        "category_id": 3,
        "category_name": "SHOES",
        "retail_price": 33.4,
        "discount": 3.4,
        "sale_price": 30
    }
]
}

Now test the endpoint by requesting a specific set of fields using URL parameters:


$ curl http://localhost/products.php?fields=product_name,category_name,retail_price

The response should only include the selected fields:

{
    "data": [
        {
            "product_name": "5G PHONE",
            "category_name": "ELECTRONICS",
            "retail_price": 420.25
        },
        {
            "product_name": "LED TORCH",
            "category_name": "ELECTRONICS",
            "retail_price": 5.35
        },
        {
            "product_name": "WOOLEN SOCKS",
            "category_name": "CLOTHING",
            "retail_price": 9.99
        },
        {
            "product_name": "LONG SLEEVE POLO",
            "category_name": "CLOTHING",
            "retail_price": 18.25
        },
        {
            "product_name": "SUEDE SHOE",
            "category_name": "SHOES",
            "retail_price": 33.4
        }
    ]
}

To fetch only one field, such as product_name, try the following:


$ curl http://localhost/products.php?fields=product_name

The resulting data should contain only the product_name field:

{
    "data": [
        {
            "product_name": "5G PHONE"
        },
        {
            "product_name": "LED TORCH"
        },
        {
            "product_name": "WOOLEN SOCKS"
        },
        {
            "product_name": "LONG SLEEVE POLO"
        },
        {
            "product_name": "SUEDE SHOE"
        }
    ]
}

If a user mixes valid and invalid field names—such as product_id,zzzz,product_name,yyyyyyy—only valid fields will be returned:


$ curl http://localhost/products.php?fields=product_id,zzzz,product_name,yyyyyyy
{
    "data": [
        {
            "product_id": 1,
            "product_name": "5G PHONE"
        },
        {
            "product_id": 2,
           "product_name": "LED TORCH"
        },
        {
            "product_id": 3,
            "product_name": "WOOLEN SOCKS"
        },
        {
            "product_id": 4,
            "product_name": "LONG SLEEVE POLO"
        },
        {
            "product_id": 5,
            "product_name": "SUEDE SHOE"
        }
    ]
}

However, if the request contains only invalid field names, such as:


$ curl http://localhost/products.php?fields=xxx,yyy,zzz

The API will return an error message:

{
    "message": "No valid fields are supplied"
}

Conclusion

In this tutorial, you’ve successfully built a PHP-based REST API that supports dynamic field selection using MySQL on Ubuntu 20.04. You can now tailor this foundation to accommodate more advanced features depending on your project’s requirements.

Source: vultr.com

Create a Free Account

Register now and get access to our Cloud Services.

Posts you might be interested in: