Making an API endpoint in WordPress using add_rewrite_rule

There are many ways to build an API in WordPress. I personally like to build them against the base URL. If I was building one for this site it might be https://php-built.com/php-built-api/v1/.

I like to prefix my API’s to help prevent conflicts. If I was using a framework like Laravel or Slim I might take a different approach, but this is a post about WordPress.

To build an API in WordPress using your own URL it is dead simple. You only need to use three hooks: query_vars, template_include.

We will be using PHP 5.4 and WordPress 4.2.

Register your API endpoint

For now, let us focus on establishing our endpoint. Later we can get into making use of it.

To register the endpoint against the base we can use add_rewrite_rule. This is the simplest way to get a custom URL going. We can use the admin_init hook to do this.

add_action('init', function() {
$regex = 'php-built-api/v1/([^/]*)/([^/]*)/?';
$location = 'index.php?_api_controller=$matches[1]&_api_action=$matches[2]';
$priority = 'top';
add_rewrite_rule( $regex, $location, $priority );
});

In this example, we are not following a pattern like REST or RPC. We are simply drawing out the basics so we can use these ideas to build any API we want.

Don’t forget to flush your rewrite rules.

The basics of add_rewrite_rule

If you are not familiar with add_rewrite_rule it is fine. We only need to know three things when building the example API: matching the URL, fetching our desired location, the priority of the endpoint.

Matching

Matching the URL in WordPress requires regular expressions. If you are not familiar with RegEx you need to be at this point (regex101.com is a great playground).

Note: WordPress hacks RegEx by escaping / for you and adding ^ to the beginning of your expression. I imagine they are trying to be developer friendly and keep sloppy plugins for disturbing user experience.

RegEx 101 API example

Location

Now, every match we find is passed into $location as the $matches variable. This lets us assign them to specific query variables in the URL. In RegEx, matches are defined between parenthesis ().

In our code we are looking for two matches 'php-built-api/v1/([^/]*)([^/]*)/?'. We then assign them to the location 'index.php?_api_controller=$matches[1]&_api_action=$matches[2]'.

API WordPress Figure A1

A URL request to https://php-built.com/php-built-api/v1/posts/update/ would fetch our location and assign the matches as directed https://php-built.com/index.php?_api_controller=posts&_api_action=update.

Priority

Since WordPress has lots of rewrites already registered in the options table under the option_name rewrite_rules we need to be sure ours is the first one it looks for. Otherwise, a page named php-built-api might cause issues for us in the future.

We tell add_rewrite_rule to send our rewrite to the ‘top’ of the list and all is well.

$priority = 'top';
add_rewrite_rule( $regex, $location, $priority );

Establishing our custom matches as safe query variables

WordPress wants to keep you safe, believe it or not, and so we need to establish that our custom query variables are part of the program. In our case _api_controller and _api_action are the query variables in the $_GET request we want to make available.

Using the query_vars filter hook we can drop them into WordPress as safe. This will let us access them in our templates.

add_filter( 'query_vars', function($vars) {
array_push($vars, '_api_controller');
array_push($vars, '_api_action');
return $vars;
} );

Plain and simple.

Constructing our Response

Finally, we are on to the fun part and really the thing we care about most. The Response.

To construct a response we need to override the default templating engine using the template_include filter hook. This will let us define what we want WordPress to send back.

We don’t want the themes index.php template file after all.

To detect a request coming to the API use get_query_var and check for our query variables. If they are present we should send a custom response.

add_filter( 'template_include', function($template) {

$controller = get_query_var('_api_controller', null);
$action = get_query_var('_api_action', null);

if($controller && $action) {
$template = __DIR__ . '/api/v1.php';
}

return $template;
}, 99 );

This example will produce the response using our v1.php file. We can respond with JSON, XML or any other format we like. Just be sure to set the appropriate headers in the new template file.

JSON Response

If we wanted to send JSON we can use wp_send_json in our v1.php file.

wp_send_json(['api' => 'v1'] );

If we look at the HTTP response we will get exactly what we want.

HTTP/1.1 200 OK
Content-Length: 12
Content-Type: application/json; charset=UTF-8

{"api":"v1"}

Simple Image Optimization and Reduction

Image optimization and reduction is critical to website performance and speed. File minification and gzip work well for text bases assets like HMTL, CSS, SVG and Javascript; but images don’t take advantage of these types of compression.

To minify images I like to use reducers and compressors that don’t visibly damage image quality.

Mac Image Optimization

On OS X I like to use ImageOptim for a solid image pass through. I also use ImageAlpha for PNG images that require more refinement.

Photo of imageoptim

 

Ubuntu Server Image Optimization

For Ubuntu Servers where I’m dealing with client uploaded images or images that are not contained within an application I use two image optimizers to pass over and help a little. This technique is not meant to be highly effective.

These tools are jpegoptim and optipng. To install them on Ubuntu use apt-get.

apt-get install optipng
apt-get install jpegoptim

Ubuntu apt-get installs an old version of optipng but I have not had any issues with it.

Usage

For JPEG files jpegoptim works well when used with the find command.

find YOUR_DIR_NAME_HERE -iname '*.jpg' -type f -exec jpegoptim {} ;

For PNG files I use optipng.

find YOUR_DIR_NAME_HERE -iname '*.png' -type f -exec optipng -o3 {} ;

Enabling Performance Monitoring for MariaDB and MySQL

Enabling performance monitoring for MariaDB and MySQL works the same way.

Performance monitoring for databases is extremely helpful when you have records in the hundreds of thousands using complex joins. It also helps when you want to do basic performance monitoring overall.

In the past you could use general_log, log_slow_queries and SHOW PROFILES. Since MySQL 5.5 you have access to much more detail through performance_schema.

To see if you already have performance monitoring enabled check your performance_schema variables using the your database console. If it is set to OFF you need to enable it.

I’m using MariaDB as my database.

MariaDB [(none)]> SHOW VARIABLES LIKE 'perf%';
+--------------------------------------------------------+-------+
| Variable_name                                          | Value |
+--------------------------------------------------------+-------+
| performance_schema                                     | OFF   |
| performance_schema_accounts_size                       | 10    |
| .......                                                | ...   |
| performance_schema_users_size                          | 5     |
+--------------------------------------------------------+-------+

When you enable performance monitoring do so only on your development environment. You will not see a major speed reduction but on production you can.

Enabling Performance Monitoring

After you install MariaDB using Homebrew create a hidden my.cnf file in your home directory. For MySQL you may need to locate an already existing my.cnf.

> cd ~
> touch .my.cnf

Open your favorite editor and edit the file. Add performance_schema to my.cnf, set performance_schema_events_statements_history_long_size to 10000, be sure you have [mysqld] set at the top, and save the file.

[mysqld]
performance_schema
performance_schema_events_statements_history_long_size=10000

You can also set other configurations in the my.cnf file if you like. Alternatively you can set server system variables in the command line or the mysql client.

Restart MariaDB or MySQL server.

> mysql.server restart

Enter the database console again and see if performance_scheme is enabled.

MariaDB [(none)]> SHOW VARIABLES LIKE 'perf%';
+--------------------------------------------------------+-------+
| Variable_name                                          | Value |
+--------------------------------------------------------+-------+
| performance_schema                                     | ON    |
| performance_schema_accounts_size                       | 10    |
| .......                                                | ...   |
| performance_schema_users_size                          | 5     |
+--------------------------------------------------------+-------+

events_statements_history_long

Now you can start enabling the features you want to use. I personal use the events_statements_history_long table introduced in MySQL 5.6.

events_statements_history_long will give you access to a detailed report on the last 10,000 queries run.

To enable events_statements_history_long monitoring open the MariaDB console and set your setup_consumers to YES. You must do this each time you start MariaDB, even after OS X launch.

MariaDB [(none)]> UPDATE performance_schema.setup_consumers SET enabled = 'YES' WHERE name = 'events_statements_history_long';

From here you can run SELECT queries on the performance_schema.events_statements_history_long table to review and profile your results.

When you make changes to your my.cnf file and restart your Database you will need to re-enable setup_customers.

You can review all the setup_customers using SELECT.

MariaDB [(none)]> SELECT * FROM performance_schema.setup_consumers;
+--------------------------------+---------+
| NAME                           | ENABLED |
+--------------------------------+---------+
| events_stages_current          | NO      |
| events_stages_history          | NO      |
| events_stages_history_long     | NO      |
| events_statements_current      | YES     |
| events_statements_history      | NO      |
| events_statements_history_long | YES     |
| events_waits_current           | NO      |
| events_waits_history           | NO      |
| events_waits_history_long      | NO      |
| global_instrumentation         | YES     |
| thread_instrumentation         | YES     |
| statements_digest              | YES     |
+--------------------------------+---------+

MySQL also has a quick start guide for the performance schema.

Uninstall MySQL from Mac OS X Yosemite 10.10

To uninstall MySQL from your Mac first backup your databases and then stop your MySQL server.

In the terminal run these commands.

sudo /usr/local/mysql/support-files/mysql.server stop
sudo rm /usr/local/mysql
sudo rm -rf /usr/local/mysql*
sudo rm -rf /Library/StartupItems/MySQLCOM
sudo rm -rf /Library/PreferencePanes/My*
sudo rm /Library/LaunchDaemons/com.mysql.mysql.plist
sudo rm -rf /Library/Receipts/mysql*
sudo rm -rf /Library/Receipts/MySQL*
sudo rm -rf /private/var/db/receipts/*mysql*
sudo rm -rf /var/db/receipts/com.mysql.*

You may see errors if your copy of MySQL is configured differently but this will cover most or all uninstallation.

For later versions of OSX there is a great answer on superuser.com for uninstalling MySQL. The main difference in older versions of Mac OS X is the use of the /etc/hostconfig file.

Once you are finished uninstalling MySQL restart your Mac.

Install MariaDB on OS X Yosemite 10.10

Use Homebrew to install MariaDB on your Mac. MySQL has a dmg, MariaDB does not. I recently switched to MariaDB for three main reasons:

  1. The influence of big names like Google and Wikipedia making the move as well.
  2. Michael Widenius the creator of MySQL is running the project.
  3. Works with SequalPro, Querious* and Navicat out of the box.

If you already have MySQL installed you will need to backup all your databases and uninstall MySQL. Remove the plist LaunchDaemon, if you added one to Yosemite. Restart your computer.

Article installation notes

Ben Stillman has a great article over on MariaDB.com/blog covering how to install MariaDB, check the link below and my notes to go with it.

Article on how to install MariaDB 10.

Be sure to watch the OS X terminal as you go through the article for information on different levels of configuration.

  • Step 4: You will need to install Homebrew using instructions from http://brew.sh/
  • Step 6: Watch the terminal for your start up plist LaunchDaemon instructions.
  • Step 7: You are probably installing a later version. You will want to cd into the directory with the version you installed.
  • Step 11: If you see MySQL in your version you need to uninstall MySQL. You should’t run MySQL and MariaDB.

Quick and dirty install

For those who have a little more experience these are the brief installation instructions.

I’m installing 10.0.17 your version might be different.

> xcode-select --install
> ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
> brew update
> brew install mariadb
> unset TMPDIR
> cd /usr/local/Cellar/mariadb/10.0.17/
> ln -sfv /usr/local/opt/mariadb/*.plist ~/Library/LaunchAgents
> mysql_install_db
> mysql.server start
> mysql_secure_installation
> cd ~
> touch .my.cnf # your mysql config

Now log into MariaDB and check your version.

>  mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or g.
Your MariaDB connection id is 6
Server version: 10.0.17-MariaDB Homebrew

Copyright (c) 2000, 2015, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.

MariaDB [(none)]> select @@version;
+-----------------+
| @@version       |
+-----------------+
| 10.0.17-MariaDB |
+-----------------+
1 row in set (0.00 sec)