Headless WordPress

Andrew Kepson

January 29th, 2022

Building a Headless WordPress Site with Node & GraphQL

The author of this blog post, Andrew Kepson, standing in front of the Rocky Mountains in Colorado.

Andrew Kepson

January 29th, 2022

In my last post I wrote about creating our own headless WordPress connection using the WordPress REST API with Gatsby. It is interesting to explore how tools I’ve been using work under the hood, and I wanted to see if I could peel the onion back another layer and build a headless WordPress website without Gatsby. Gatsby is a React framework that provides a plethora of great tools for building modern websites, including connecting to WordPress as a headless CMS, and I am used to letting it perform a lot of the heavy lifting when it comes to optimizing a site, like handling routing, pre-fetching resources, and building pages. 

I love using Gatsby but I wondered, if I woke up tomorrow and Gatsby had disappeared, what would I do to continue building JavaScript frontends as a headless WordPress developer? This is my journey toward finding out.

Why Build with Headless WordPress?

WordPress has remained ubiquitous for several years now as the content management system of choice. An open source, tried and true platform, WordPress experience is important for many web developers and marketers alike. From the White House to thousands of small local businesses and blogs around the world, WordPress powers over 40% of sites on the web today. I love that, with WordPress, anyone with hosting can get their content up on a real website and share it with the world.

Additionally, WordPress is familiar to millions of people who are building,managing, optimizing, and writing content for websites. It is a robust, easy to use, proven content management system.

Connecting to Headless WordPress with WPGraphQL

In my last post I learned about connecting to WordPress using the REST API, but in reality it makes more sense to stick with WPGraphQL. Actively developed by Jason Bahl from WPEngine, WPGraphQL provides a new endpoint for your WordPress site to query with GraphQl requests, and provides a GraphiQL IDE in your WP admin panel where you can see the schema and visually build your requests.

Headless WordPress GraphQL IDE
WPGraphQL IDE for building requests for headless WordPress sites.

Installing the Necessary Headless WordPress Plugins

If I were dropped off an a deserted island, had to build a headless WP site to get home, and I were only allowed to install three plugins, these are the plugins I would choose:

  • WPGraphQL – As discussed above, WPGraphQL creates our graphql endpoint that we will query to get our WordPress data.
  • Yoast SEO – In addition to some other SEO-related benefits, Yoast SEO makes it easy to update and optimize our Title Tags & Meta Descriptions.
  • WPGraphQl Yoast SEO AddonThe WPGraphQL Yoast Addon will add SEO endpoints to our WordPress GraphQL schema that we will query to dynamically render our Title Tags & Meta Descriptions, essential for any blog site.

If you have installed these plugins, you should be able to visit the GraphiQL IDE in your WordPress admin panel. Now we can get started on building a server to connect with it.

Installing Our Dependencies

In our Node application, we’re going to use a handful of dependencies to make this work:

  • DotEnv – We’re just using this to store our headless WP GraphQL endpoint. Of course, if we were connecting to any other external APIs, we would use this for our authentication variables.
  • Express – Like most JS developers, we’ll be using Express to build our server and handle requests so we can render the appropriate HTML and send it back to the user.
  • EJS – EJS is a templating language that lets us dynamically inject JavaScript into our HTML templates, very similar to passing props in React. 
  • GraphQL & GraphQL-Request – Since we want to use the optimizations provided by WPGraphQL, we’ll use these packages to send GraphQL queries from our server.
npm i dotenv express ejs graphql graphql-request

Structuring Our Project

For this simple application, an MVC-type model makes sense – we just don’t need any Models because WordPress is our database. After setting up our dependencies, I’m going to create a server.js file in the root directory, and four additional directories: controllers, public, routes, and views. I’ve also included .gitignore and .env files for deployment.

Setting Up Our Headless WordPress GraphQL Endpoint

In our WordPress admin console, navigating to WPGraphQL settings allows us to set the slug for our endpoint, which defaults to ‘graphql,’ and provides us with the endpoint we will query to access our WP data:

WPGraphQL settings for using headless WordPress with GraphQL
WPGraphQL Settings Page

Since we’ll be passing this endpoint to our queries on the server, I’m setting it up in my .env file as a variable: WP_GRAPHQL_ENDPOINT. If I wanted to protect my WP data, I could set up authentication in the WPGraphQL settings, but since everything on my WordPress is publicly available anyways, I am not worried about that at this point.

Building a Headless WordPress Server with Express

I don’t want to deal with caching and building the site at this point, so I’m going to use server-side rendering with Express and EJS. Essentially I’m using WordPress as a database with some useful configurations for content management, and I’ll decide how to manage access to and display that data in my Express application.

In this case, I’ll set up a fairly basic boilerplate express server, initializing EJS as the templating language, setting aside a blog Router to handle the dynamic rendering, and covering the static pages.

NodeJS server for a headless WordPress site
Express server at server.js in the root directory

Querying With GraphQL in Express

In order to show users blogs from my headless WordPress instance, I’m going to need to listen for the post the client wants on the server, query my GraphQL endpoint, and render the appropriate data for the client. In order to perform GraphQL queries in Node, I’m importing a package called graphql-request. This package gives us access to a couple of useful methods that let us perform GraphQL queries very easily. In fact, the syntax is almost identical to what I am used to doing with page queries in Gatsby.

In my controllers directory, I’ve created a file called postControllers.js, where I will define any functions used to access WordPress post data. Getting all of the posts from WordPress is simple enough:

Get all headless WordPress posts with GraphQL
GraphQL Query to get all WordPress posts from WPGraphQL

As a reminder, we know exactly what to query because we can build out requests right in our GraphiQL IDE in WordPress.

I also need to handle requests for a single post. If I were migrating from a traditional WordPress setup to using a Node app to handle my frontend, I would want to make the migration as seamless as possible, so I’m going to listen for the slugs of the blog posts – that way the routes stay exactly the same, and I won’t have to deal with redirecting all of the posts if I use the same URL structure. Additionally, I can pass the slug as the ID to query the specific post from WPGraphQL:

GraphQL Query to get a single post from WPGraphQL

In this function I am taking in a slug variable, which is passed in the third argument of my request() function call. That slug is passed to the GraphQL request, which expects a slug to use as the ID to determine which post to request from my WordPress GraphQL API. Now I just need to pass these functions the appropriate data, render it with my EJS templates as necessary, and send it to the user.

I’ve set up an Express Router to handle any routes at /blog; in this case, I only want to listen for GET requests at the blog index page, or individual blog posts:

Headless WordPress Express Router to handle requests for blog posts
Express Router to handle requests to our headless blog

Injecting WordPress Data Into EJS Templates

In our EJS templates, we’re handling how this data will be displayed to the user. On the Blog page, all of the posts are rendered on cards linking to the post, ensuring an entry point to the blog posts is always open on my site for search engines to crawl and index:

EJS blog page template for headless WordPress blog
EJS template to render blog page

Learn more about how Google handles crawling, rendering, and indexing content that is dynamically rendered with JavaScript here.

For the individual posts, I’m going to pass the SEO title to my <head> partial and render an H1 with the title, and render the content HTML directly in the page:

EJS template to rendder a blog post with headless WordPress
EJS template to render a single blog post

Now we have a functioning headless WordPress blog:

Headless WordPress blog page
Lorem Ipsum Blog – Powered by headless WordPress

One of the reasons that Gatsby is so fast is because of its Gatsby Link component, which uses the Intersection Observer API to prefetch resources for internally linked pages that are in the user’s view. Next.js also offers the next/link component to accomplish the same purpose. I want to optimize the speed of this site, so I’m going to install a JS package called QuickLink. QuickLink works similarly to prefetch internal pages when the link is in the user viewport. I can install it via CDN my head element EJS partial:

EJS head element partial for a headless WordPress blog site
EJS head partial

My scripts partial is simply a place to easily manage any internal scripts that I want to load from the scripts folder in my public directory. In this case I am loading only one; a script to initialize QuickLink on my frontend:

QuickLink for headless WordPress site
QuickLink initialization

QuickLink doesn’t start doing anything until the browser is idle, and in addition to loading the library asynchronously, we wait until the window is loaded to initialize it to prevent the package from negatively impacting our time to interactive. The quicklink.listen() method accepts an object of options. In my case, I am telling it to only prefetch links in the header or my index of blog cards on the /blog page, and to only prefetch two links at a time. By default, QuickLink only prefetches internal links.

Headless WordPress with Node & GraphQL: Final Thoughts

While I won’t be opting to give up the tooling available to be with React frameworks, it was a lot of fun to take a peek under the hood and build out some of the capability I am used to getting for free with other frameworks. It proves that headless WordPress has become an accessible content management tool for all types of applications and developers.


Check out the result for yourself on the Lorem Ipsum blog. I think there’s a good chance I will make some different versions of this blog – it might be fun to connect to a React frontend instead of EJS, or add some additional optimization features to improve the speed. Please reach out if you have any ideas for improvements or additional optimizations. View the code here.

Featured Image by Mika on Unsplash

January 29th, 2022

This post is powered by headless WordPress.