Sammi Sinno

Development - Off The Beaten Path

A development blog that explores everything and puts an emphasis on lesser known community driven projects.

Home

Headless Wordpress

Friday, January 31, 2020

Lets take a second to think about WordPress and its original mission. It started off as a blogging platform, but quickly turned into something much, much more. It wasn't long before developers were not only spinning up blogs with WordPress, but full fledged sites that incorporated ecommerce platforms, search indexes and the ability to organize and change content with ease. I'm not just talking about blog posts, but widgets, pages, navigation menus as well as custom module content.

So why do developers gravitate towards WordPress? Is it because it was built using PHP? I think not, personally I work primarily with C# and even though PHP isn't my language of choice, I still prefer WordPress over many other CMS systems. It could be argued that PHP gives these types of CMS systems and frameworks the platform to be successful (Drupal, Laravel, Joomla ect...), but I don't believe that is the main driver in the success that WordPress has enjoyed. Their success comes down to two things, the amazing administrative dashboard they've built, and their very intuitive API. The former reels in users and the latter attracts developers.

Priorities

It's safe to say that WordPress has always put user experience first. For instance, Drupal just came out with a new configuration management system that allows developers to sync all of the CMS settings in source control. For a developers workflow this is amazing and makes things a lot easier when spinning up new environments. WordPress on the other hand just came out with Gutenberg, which provides a robust editing experience for end users. While the WordPress core team has put less emphasis on building out features for developers, they have enough confidence that the community will fill that void due to the flexible API they've created.

Blocks Not Blobs

Advanced Custom Fields

One critique of WordPress is that it only has support for content in the form of blobs (posts and pages). This is where the immense WordPress community comes into play. While there are dozens of plugins that can help developers transform their content to blocks, the community has chosen one in particular to support and that is ACF. Advanced Custom Fields allows developers to add fields with any data type to their posts essentially turning posts into content types. This plugins comes with an incredible amount of settings that you can change for every field, allowing one to be able to easily change the style and placement of the field along with adding descriptions and tooltips. Adding field groups to posts is very easy, and can be done using the Rules portion of the field group. You can even create your own custom rules if there isn't one already there that fits your needs.

The Pro version of ACF comes with additional field types, but most importantly the ability to apply field groups to settings pages and save them as WP options. I highly recommend purchasing the Pro version as it is only $70 for a developers license and I do believe it is very important to support the developers that maintain these amazing plugins.

One extremely important feature built into ACF is the ability to save your field groups as JSON, by simply creating a folder named acf-json in your themes folder. This will allow you to sync all of your settings via source control and will make all field groups available in every environment without having to create them manually every time.

Custom Post Type UI

The CPT UI allows you to more easily organize your posts into post types (content types) and creates separate tabs for them in the Admin UI. There is also an ACF rule that allows you to add field groups to custom post types. After creating new types you can then extract those type definitions as PHP and place them in source control to sync across environments.

    $labels = array(
        "name" => __("Emails", "custom-post-type-ui"),
        "singular_name" => __("Email", "custom-post-type-ui"),
    );

    $args = array(
        "label" => __("Emails", "custom-post-type-ui"),
        "labels" => $labels,
        "description" => "",
        "public" => true,
        "publicly_queryable" => true,
        "show_ui" => true,
        "delete_with_user" => false,
        "show_in_rest" => true,
        "rest_base" => "",
        "rest_controller_class" => "WP_REST_Posts_Controller",
        "has_archive" => false,
        "show_in_menu" => true,
        "show_in_nav_menus" => true,
        "exclude_from_search" => false,
        "capability_type" => "post",
        "map_meta_cap" => true,
        "hierarchical" => false,
        "rewrite" => array("slug" => "email", "with_front" => true),
        "query_var" => true,
        "supports" => array("title", "editor", "thumbnail"),
    );

    register_post_type("email", $args);

Headless

If you really want to have an enterprise level architecture these days, you have to go headless. While WordPress themes are very good for simple sites with little developer support, when trying to create a more detailed and advanced UI it only tends to get in the way (and that goes for every CMS theming capability, not just WordPress). All content is easily accessible from the built in wp-json API, and if the end point doesn't already exist it is very easy to add like so:

<?php

 function get_site_settings(WP_REST_Request $request)
 {
     return [
         'time_zone' => get_option('timezone_string'),
         'time_format' => get_option('time_format'),
         'date_format' => get_option('date_format'),
         'from_email_address' => get_option('from_email_address')
     ];
 }

 add_action('rest_api_init', function () {
     register_rest_route('site/v1', '/settings', array(
         'methods' => 'GET',
         'callback' => 'get_site_settings',
     ));
 });

 ?>

Nuxt.js / Next.js

When WordPress was first created there weren't a lot of options for creating front ends, so the dynamic theming and view rendering was a prefect fit for its time. Now there are so many options that focus primarily on building UIs, two of note are Nuxt and Next. The reason for using one of these two frameworks is they are both built using very popular SPA frameworks (Nuxt is based on Vue and Next is based on React) and both support SSR (Server Side Rendering). Server side rendering is very important for a lot of reasons, first and foremost search engines still have a lot of trouble parsing single page apps and the site will not be indexed properly causing organic search results to drop in rankings. The other reason is it is much more performant. External facing content driven sites are normally accessed from all types of devices, not just desktops and sending all the JavaScript that is necessary to render a SPA to smart phone or tablet will slow the rendering of the site and cause users to drop off.

NGINX

If your going headless you'll almost certainly need something to glue the technologies together and this is where NGINX comes in. NGINX can operate as a web server and proxy for the front end app as well as WordPress by handling the requests for static files along with proxying requests to node and php-fpm servers. The beauty of having NGINX is it makes it all seem like one application and can help you to not only avoid CORS issues, but also keep the same architecture in your local environment as you would have in your deployed environments.

Here is an example of an NGINX conf that you would use for proxying WordPress requests. You'll see that all PHP requests get handled by php-fpm and all static files are served directly by NGINX. While you can serve static files with php-fpm, it is not recommended as you will run into strange issues with Content-Type headers as it cannot properly discern the mime types for those requests.

# Deny access to any files with a .php extension in the uploads directory
# Works in sub-directory installs and also in multisite network
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~* /(?:uploads|files)/.*\.php$ {
    deny all;
}

# need to properly process wp-json routes
location ~ ^/wp-json/ {
    rewrite ^/wp-json/(.*?)$ /index.php?rest_route=/$1 last;
}

location ~* "(^/wp-.*|\.php)" {
    resolver $NGINX_RESOLVER valid=10s;
    root /usr/share/nginx/wordpress;

    index index.php;

    # Add trailing slash to */wp-admin requests.
    rewrite /wp-admin$ $scheme://$host$uri/ permanent;

    location ~ \.php {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass $WP_CONTAINER_ADDRESS:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    # Directives to send expires headers and turn off 404 error logging.
    location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
        access_log off; log_not_found off; expires max;
    }
}

Security

There are an incredible amount of sites that run on WordPress, somewhere around the range of 35%, and because of this, most malicious bots are crafted to exploit well known WordPress vulnerabilities. Thankfully there are a few plugins that can help to safe-guard your site from attacks as well as give you more incite into all the events that occur on your site (publishing a post, admin logic ect..). While there are many plugins in the community that have comparable feature sets there were a few that stuck out in particular.

Loginizer

Lognizer does a couple of things very well, most importantly it helps to protect against brute force attacks, which will most definitely happen if you are running WordPress with your site. Attackers go straight for the common /wp-admin/ URL and start making login attempts with the default admin user. Lognizer solves this problem in a couple of ways, firstly, it will lockout users (or bots in this case) for a specified duration after three failed login attempts. You can change the number of login attempt failures before a lockout occurs as well. It will also log where the login attempt came from (by IP) and how many attempts they have made.

Another useful feature that Lognizer comes with is the ability to change the default admin URL from /wp-admin to whatever you want. This is a very useful trick since bots can only submit forms that they know about. Unfortunately, this only comes with the paid version, but like I said before I believe it is important to support these developers for the time and effort put into these plugins.

Another very important security trick to note when first setting up your WordPress site is to change the default admin user name to something other than admin. Brute force attacks always choose admin as the username, so choose something totally random!!!

WP Security Audit Log

Next up is WP Security Audit Log, with only the basic version you get something that is very essential to the security of the site, and that is an audit log. The audit log provides a history of everything that has happened on your site, and each record consists of the severity of the action, date, user, IP, object affected, even type and message. If you so choose to upgrade you can get features like reporting, notifications as well as the ability to separate the logs from your current database and into another provider (such as slack). Needless to say having the audit log alone is a good enough reason to invest in this plugin.

Members

You can't really mention security without talking about the authorization portion of it, which in this case a plugin like Members solves. It is extremely easy to get up and running with and allows you to set granular policies (what they call capabilities) for each exiting role as well as add new roles. You can also add your own custom capabilities if the one you are looking for does not exist.

Conclusion

The infrastructure is beyond the scope of this article, but it is important to choose technologies that work well together and I believe that AWS ECS Fargate and Docker are the perfect choice for an enterprise level solution. Severless containers will help to keep operating costs extremely low as well as help to replicate the entire application locally. Not to mention the fact that with Fargate you get automated scaling and infrastructure. If done properly Docker will also help to accelerate developer onboarding, eliminate app conflicts as well as allow your team to ship software faster.

Honorable Mentions

A few more plugins that I left out of this article but deserver honorable mentions are:

  1. https://wordpress.org/plugins/wp-cfm/
  2. https://wordpress.org/plugins/acf-to-rest-api/
  3. https://github.com/hoppinger/advanced-custom-fields-wpcli
  4. https://wordpress.org/plugins/post-type-switcher/
  5. https://wordpress.org/plugins/tablepress/
  6. https://wordpress.org/plugins/amazon-s3-and-cloudfront/