Walkthrough of a Real, Production Headless WordPress App

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Just wanted to let you know that I was subscribed to your channel earlier and am interested in your content, but the characters are not my thing and I unsubscribed. No need to change anything just for me, but I would like to know why people would unsubscribe, so I thought I might as well share that with you.

๐Ÿ‘๏ธŽ︎ 3 ๐Ÿ‘ค๏ธŽ︎ u/NNTNDRK ๐Ÿ“…๏ธŽ︎ Dec 29 2020 ๐Ÿ—ซ︎ replies

I just posted a this detailed walkthrough video showing how I built both the frontend & backend for a headless WordPress app that uses Gatsby, React, Apollo Client, WordPress and WPGraphQL, for those interested.

๐Ÿ‘๏ธŽ︎ 1 ๐Ÿ‘ค๏ธŽ︎ u/kellenmace ๐Ÿ“…๏ธŽ︎ Dec 29 2020 ๐Ÿ—ซ︎ replies
Captions
hey franny my dear did you know there's an object-oriented way to get rich what an object-oriented way to get rich yeah inheritance [Music] welcome to headless wp rocks today is going to be a bit of a show and tell episode i'm going to do a detailed walkthrough of how i built and launched a real production headless wordpress application called run through history i think this is going to be a pretty cool case study you'll be able to see how i built out the front end react application to be very responsive and interactive and provide a nice snappy user experience to the users and how i use the wordpress rock solid content management experience on the back end and then use wp graphql to allow the two to communicate with one another we'll get started by just hearing the story of like where the app came from and why it exists so you're kind of familiar with it after that i'll give you a tour as though you're a user of the app of kind of its features and and what the app can do all the screens it has how you interact with it and so on and then lastly we'll dive into the code i'll show you the front end app and how all of that's wired up where it's hosted and how deploys work how it communicates to the headless wordpress backend and we'll also look at that back end as well how wordpress is set up the custom post tapes and taxonomies i'm using the custom fields i'm using and how i'm updating the graphql schema to do what we need it to do for the front-end app so now let me tell you that story of how this app came to be so i'm recording this at the end of 2020 and unfortunately for for many months uh the cova 19 virus has been adversely affecting many many people and one of the groups that it's affecting is athletes more specifically runners a good friend of mine from college by the name of rich runs a race company called trivium racing so he puts on all kinds of 5k 10ks half marathon marathon etc runs and he and his wife run this racing company together and i've worked with them on a few projects in the past but he came to me with this great idea he said because so many runners are unable to go to these large group events that they're used to participating in due to the virus what if we partner up together he and i and put together a virtual running event other organizations are doing these like big name companies like disney and so on are doing these type of virtual events so he thought there's a big market for that and a need for to provide the ability to do these running events to to runners so he came up with this great idea for an event called run through history the idea of with this event is that runners start at uh year zero and then every mile that they run that represents one year a.d so then as they run along the timeline of history when they hit certain historical landmarks or milestones they earn different things so there's a gamification aspect to it where you can earn different badges and things as you progress through the timeline at a certain point you also win medals you win the bronze medal then the silver and then if you reach the end reach all the way to 2020 like the present day year then you earn the gold medal and it's event that runs for 12 months it's like a full year-long event so as runners you know go go out and do their runs they can log them in this app and then see themselves progressing along the timeline and earning different metals and badges and things like that so it's kind of fun there's also a competitive side to it as well so there's a leaderboard that lists you know where you rank uh if you're an athlete if you're a runner participating in the event you can see where you are as of you know the current date and then how many miles you need to go to uh pass you know the person ahead of you and to progress you know up to the to the top of the leaderboard or however close you can make it so we both thought this sounded like a great idea and something runners would enjoy doing so we decided to go ahead and partner up and launch runthroughhistory.com so let me give you a tour of the app next so the front end react app lives here run through history.com this is a progressive web app hosted on netlify and built using react and gatsby and apollo client and a few other technologies this repo is on github so whenever i push a change to the master branch it automatically triggers a rebuild on netlify and then this front end gets redeployed here this is a progressive web app as i said and i have a prompt that pops up like if somebody is a registered user has an account and they've been to the site multiple times i have a prompt that pops up they ask them if they want to install you know the pwa to their home screen just for convenience and if they do that it looks something like this at least an ios it does and similar experience for android where they get an icon on their device's home screen so it's pretty cool and then when they open that up it looks very similar to like a native application running on the device even like when you use something like the ios app switcher it looks like this or it gets its own icon and name and then the screenshot as you can see here is like is in standalone mode so it doesn't have like any other browser chrome or anything like that so to the user looks you know very similar to a native app that they would have installed via the app store but little did they know it's built using completely you know web technologies so that's pretty cool the back end lives here at api dot dot this is a wordpress site hosted on a digitalocean droplet you can see i have a few custom post types here runs and awards i've modified the user object so we have a few taxonomies and custom fields and things i'm using for that so we'll take a look at all of that let's move away from production though and we'll look at like my local version of the site here just so we can make changes to it not worry about messing up production so this is what it looks like like on a mobile device to give you a sense of it from here you know i i'm a logged out user at this point um so i can my choices are to go to sign in sign up i can you know read some info about like what the info what the event is all about here and then click sign up which sends me to the same place as here so a few forms you know if they hit sign in they already have an account um they end up here if they hit sign up they can um fill out the form to register as a new user if they forgot their password i will forgot password flow as well so let's take a look at that so let me sign in here so i'll sign into the app hit sign in so here we go uh now the app has recognized that i'm a logged in user and it's you know re-rendered some portions of the site so you can see this top menu has changed instead of sign in and sign up it now knows i'm logged in so it's re-rendered to just show sign out now and also have a brand new fixed bottom nav here so it says um home runs which would be like all the runs that i've logged so far the timeline and then the leaderboard so we'll go through like each of these now so home is the page we've already been to that just you know tells about the event if i ever head over to runs here this page is pretty pretty cool uh it's a log of all the runs that user has done through throughout the whole year and it's using infinite scroll to load this list so at first it loads something like five or ten runs something like that and then as the user as the user uh scrolls down like this you see there's a momentary delay it's much faster on production actually that is my local to load more runs but if i keep doing this eventually i'll reach the beginning of the year so there it is this is my first run i had logged when the event kicked off in early july and then going through time like here are all the runs that i personally logged for this event and you can see what my total miles are now the option to add a run as well so let's let's do that i'll tap add run and it shoots over to this form it defaults to the um the current date but the user can choose like whatever actual date they did the run they can see oh yeah they can say that they did 1.5 miles um let's let's say that day and if i hit add all right so i get a little toast notification here i get a sound effect and then things re-render once again you can see my total miles now reflects that 1.5 mile run and here it is on uh december 11th i logged a 1.5 mile run so now it's been added to the list as well uh what if i made a mistake what if i you know realized oh wait i actually went 1.6 i want credit for that extra tenth of a mile i want to remove this you can hit the x right here and i have a toast that pops up that says delete run so these are weights and takes no action then that just kind of goes away and nothing happens otherwise if they confirm it so they hit x and then say yes i do want to delete it so we get momentarily says deleting run and this is run deleted to confirm that change you can see things have re-rendered once again so now my total miles is back to what it was before i logged that run and then that run has has been removed from my list as well so that's pretty cool that's how this is how the user logs the runs that they've done throughout the year right here uh let's take a look at the timeline this would be as they earn miles they progress along um the timeline of history and earn different things like i said so this is what that looks like um so here's the timeline you can see everyone gets the start badge right here welcome to run through history let's get started and then as i scroll down i see different ones pop in so here's rome burns that i earned at uh 64 miles right here so i get a little description mount vesuvius and as i scroll down you can see some of these are grayed out and just have the question marks right there that's because i haven't earned these yet so i have my mileage is above 79 but it hasn't yet reached 220 so i i give the user a little teaser at this point they can kind of see the outline you know of what the badge is but they just have question marks and you know don't know exactly what that is uh they can also click on these to view the single pages for these awards so if i click on roman burns right here then it opens that in a new tab right here so you see rome burns image and then a little description like that so we can head back to the timeline so it's pretty cool that's what the timeline looks like that's kind of the gamification aspect that we have let me show you what it looks like when the user earns a new badge because it's kind of cool so they experience something different when that happens so right now i have 79 miles but i need to earn i need to earn 220 to get the next badge here so let's simulate that so back on runs we'll say that i ran a 200 mile run on on today's date let's just do that so i'll hit add and then remember instead of just the toast notification and the little sound effect that i got last time this time because we've earned a medal we're going to see a different experience so i'll hit add oh let's see i'm only allowing 100 miles at a time looks like i have a cap on that so that's fine let's do 100 adding so there we go i get our u rule and these messages are randomized so it makes it kind of fun they get a different message each time they log mile successfully all right so now i'm up to 208 so now let's hit add run again and this time we should earn something so let's say i did a 50 miler this time i'm going to log this run and this time i should earn the next badge all right so there you go you want a new award congratulations cool so you can see um i'm presented with the new award here i have the different toast notification and sound effects and all that kind of stuff and then it displays the title um the featured image for this award right here and then gives a description of like um what that is so now if i go back to my timeline it should have re-rendered as well to show that i now have earned the hot air balloons in warfare badge let's see if that's the case so there's mount vesuvius and sure enough here's hot air balloons and warfare cool so it knows that i've earned you know at least 220 and is you know showing me that badge there so that's all pretty cool um and lastly we have the leaderboard uh tab that we haven't checked out yet so let's do that if i tap leaderboard this is for the competitive folks so it knows because i'm it knows my sex and age group it just pulls that through in the my my group tab right here so it uh it knows you know what who the others are in my um my age group so it pulls them through and you can see where i am on the list so here's you know kellen mace i have 258 miles at this point so i can see who i've you know passed by um but who's still beating me uh who's above me on the leaderboard so for those real conf competitive folks like i said they want to see um their card here their username work its way up and be you know the top at the top here i can hit all female i'll get a loading for a second and they'll load in you know all the age groups maybe i have a friend doing the event and i want to like find where my friend is i can do that in this tab or otherwise you know all the mail participants i can do the same thing and see you know where they they all rank and i'll also find myself on this list so here's my same my same age group that we saw in the other tab because i've loaded each of these once now apollo client has cached that data so now we get an even quicker transition if i go to my group or female or male you can see they're very instantaneous because we've already fetched that data once so now it's cached in memory so that's the front end application next let's take a look at our wordpress back end so here i am on my local version of the wordpress backend and we can take a peek at some of this stuff um so let's look at awards first this custom post type um so this as you can see uh houses both the badges that folks can earn as well as the medals so i decided to make these one post type just called awards and then i have a taxonomy called award type and that's how i distinguish which ones are a badge from you know the ones that are metals here so if i go to like the wright brothers first flight i'll just click on that you can see this is what it looks like so i have um this is the title that pulls through on the front end when this badge is earned here's the description when it's earned i'm using the featured image just to house this uh badge image i have one custom field here that's award fields and that's how i know how many miles the person has to run in order to earn that badge it also corresponds to the year that this historical event took place and then as i mentioned i have the award type tax on me right here so it's pretty cool i'll click on one of the badges just to see that so like the gold medal for instance so this one's a bit shorter for the description just says you know great job title is gold metal and that's a picture of the metal that the participants actually earn at the end of the event they get an actual metal m-e-t-a-l a metal metal um in the mail at the end of the year it looks like that and they have to reach the very end of the event 2020 in order to earn that so that's what awards look like i did mention that there's one custom field here for the year so we can take a quick pause i guess to look at the acf custom fields that i've put in place so here you can see if i go to award fields right here um this should be just that one yeah so the miles required right there and i have a few others so for the run post type that we'll see next there's miles and date so i'm using acf as i mentioned advanced custom fields for that um and let's take a quick glance at the plugins while we're on that topic so we'll see uh advanced custom fields at the top of the list right there let's glance through the rest that i'm using here so this is the core plug-in for all the you know custom functionality for this app so i'll dig through what that looks like a little later beyond those i have a few others so the front end app is built on gatsby so i have this wp gatsby plugin here and this helps the headless wordpress backend integrate well with gatsby i have wp graphql which creates that graphql schema that we're then you know tweaking and modifying and ultimately using to talk to um our decoupled front end i have wp graphql for advanced custom fields which takes kind of bridges the gap between this plug-in and this plugin it just exposes all the acf custom fields in the graphql schema so that now you can have queries that include them for the authentication of users on the site i'm using wp graphql jwt authentication uh so i'll go through that in a little more detail as well and then we graph called tax query because i want to be able to run some uh taxonomy-based queries from our front-end so this plug-in allows me to do that that's kind of a tour of the plugins um let's head back to where we left off than with the post type so we had uh touched on awards but we haven't seen runs yet so this would be when the user is on this page and they hit add run and then they add a new one with the date and miles right here this is the data we're actually storing for those so since this event kicked off we've as you can see like we've had a lot of participation we have close to 21 000 runs that are 200 and something runners have logged so here's the last two that i uh just made during this little app walkthrough so you can click on one of those to see an example so you can see i have some uh acf fields here to capture you know the miles that the person uh said that they did that day the date that they did them i'm using the author to identify uh who the person is who logged these these miles and uh beyond that i'm just setting the title um this isn't displayed anywhere on the in the front end app this is just more for convenience you know for myself or my friends rich and libby who own the race company if any of us are here looking in the admin it just makes it easy for us to quickly scan the list of runs here and tell you know who it was and the date they logged it just for convenience so that was awards and runs let's take a look at the user objects because i made some changes to those as well so i'll find myself on the list and we'll take a look here so the typical wordpress profile page here but if we scroll down a bit we'll see some of my custom fields so here we go user fields so here are the total miles that this user me in this case have earned and this gets recalculated so every time a user either adds or deletes a run i have some code that sweeps through all the run post types with that person as the author and it just it just adds up all of the miles and then comes up with the total and resets this total amount right here this makes it very quick on the front end when we're doing our leaderboard page so when you when we need to pull a list of users and then all the totals i have these totals already pre-calculated you know ahead of time and saved in this field so that makes it really quick to to query for um query for that data since it's already i don't have to do any math on the fly to calculate those it's already saved i have that person's date of birth city state from when they registered and then the sex and the age group as well so the age group gets set automatically i don't actually ask the person this when they register what i do is i just ask for their date of birth right here and then using the date of birth i'm able to calculate how old is this person and then i set this for the user so in wordpress wordpress actually has the ability to have user taxonomies but they're not first-class citizens they're not fully supported so what i mean by that is like it's possible to register a new taxonomy and say that that taxonomy uh applies to the user object in wordpress but wordpress um unlike for post types wordpress doesn't give you any screens to manage uh those taxonomies for users and it doesn't do like recalculating the number of the number of terms saved things like that that you get for free when you register taxonomies for post types you don't get those for users but that capability is there and i took full advantage of this for this app because i actually have the sex and age group right here both of these are taxonomies and i'm able to quickly query for users within those groups and that's how i do these groupings right here so i need to if i need to group people by both sex and age group like this tab i'm able to quickly do that or just by all female all-male having these taxonomies applied makes that makes that very quick so that's what the wordpress back end uh looks like so with that i think we can dive into some of the code so let's head back to the front end here our front of the application and we'll take a look at the um the gatsby and react app that's used to power this next so here i am in the front end app code base here and this is a gatsby application so i'll walk through a few of the kind of key features here um this app you know you may think of gatsby as being used primarily for uh for sites that are largely static content and less so for applications but it's actually a really great fit for this project because i had some content that i needed to be static and fetched at build time like all of my awards since those are the same for every user and then everything else it made sense to just fetch on the client side so anything particular to that user like their their runs that they had that they had logged or their medals they had earned all of that is just fetched on on the client once i know who that person is and they've and they've logged in and so on so in the front of the app if i go to the gatsby config right here you can see the plugins i'm using so let's take a look at some of these so i guess we plug in manifest this is the one where you specify um some attributes of your app and based on these it knows how to present your app as a progressive web application so this is what what specifies like this logo um that i'm choosing here or icon rather that's where this comes from so the user saves it to their home screen it knows you know where to grab that icon and i have the short name as run thru history just because i couldn't fit this all in one line right there so that's how it knows um how to display that that text for instance uh when the user saves it to their home screen so that's kind of cool i'm using styled components for styling this apps i have that i have a custom web font i'm using as well for that for all my headings so that's the this kind of like hand hammered or chiseled looking font that i have right there um i'm loading that in as a custom font just locally here inside of a fonts directory so i have that plugin going and this one's important guess be source wordpress experimental so this is the new version of the gatsby source plugin for wordpress eventually i think this is going to be renamed uh to guest resource wordpress and the old one will be deprecated and this one will be used going forward for now it sells the word experimental in the name and for this uh for this plugin you have to specify the url of the wordpress backend right here so you can see i have process.env dot and then the gatsby underscore wp graphql url here so this can be passed in uh a couple ways so you can see i have this code at the top that requires a env.development file um so if you're working locally and that file is defined that's where gatsby knows where to pull the url from so you can see i'm just pointing it to my local environment while i work locally uh otherwise i netlify there is no env file locally here instead i have this set as an environmental variable on netlify so at build time that's present and it's set to the production wordpress backend so that it queries um from the the correct back end there and you can kind of disregard this i just have some stuff that i don't need to query at build time so i'm just preventing um this source plug-in from even adding these to the graphql schema because these are things that i either don't use for the app or i query them at run time on the client so i don't actually need the the plug-in to uh to do those so um so that's why i'm excluding them here i found that that makes you know my my um builds that i do locally a bit faster so let's see so next uh let's take a look at one of our pages so how about just just the home page here we'll start with with that one so if i open up the source directory and go to pages that would be my index.js so it looks like this it's kind of short and sweet i have this layout component that wraps all the pages in my app i have some seo that sets like the document title in the shown in the uh the browser tab there and i just have two components on this page a hero and a description um so that's kind of this section is the hero and then below is the description that has the sign up button in it so kind of short and sweet um let's dive into the layout a bit though cause we have some some interesting functionality that goes on there so here's what this is what layout looks like so this use auth hook that i'm calling they can tell if the user signed in and then if they are i render that extra fixed navigation at the bottom so that's this one that sits at the bottom so when the app first loads up that's not there as soon as a person is authenticated then that extra bottom nav pops up and they have access to those things like that um so you can take a look at this hook right here we'll look at authentication stuff next that might be interesting um so i'll do that i'll click in the use auth hook and that sends me here so inside of my hooks directory i have use auth and it looks something like this so there's a fair amount going on here um i have this auth provider right here that's um that's using apollo client to run this query right here uh that gets some um data stored just locally on the client so this isn't like some external graphql api we're creating this is just um a local state that we've saved via apollo so this query queries for that and then uh with that data i can tell if the user is logged in and if so who that user actually is and it passes that data down to anywhere in the app that that needs it so from anywhere we can uh destructure the value that use auth you know gives us and pull off of that info we can get um signed in like we just saw right here right in this case that's all we needed you can also pull off of this uh some other info as well like who that user is if you need info about them and then a few other functions if you want to be able to set the auth data for that person or delete the auth data for that person so let's take a look at this hook in a second before we do that though i want to look at the apollo file because there's actually a fair amount going on in this app for authentication so for this apollo app right here or apollo.js file here something a bit interesting at the top here so you can see i have persisted auth data so i'm saying if this is the browser meaning it's not you know build time on uh unnetlify servers if we're actually running in a browser if that's true then get the persisted auth data which we're storing in the user's browser pull that otherwise if this is build time just set this to null so what we do with this persisted auth data is we use apollo's reactive variables feature so i'm using this makevar function that apollo client provides and i'm initializing it to an object that looks like this i'm saying auth token set to null refresh token if we had persisted data use that otherwise null and then the user who's logged in we have that use that otherwise set that to null and i'm not storing the auth token locally just for a little added security so what we do is when the app first loads up i try to grab the refresh token and the user's details that we knew the last time the app loaded up and using that refresh token then we're able to send our query to the to the back end and actually get and set this auth token so we know who they are so we have all that going on so this um gets initialized here and i also have in apollo's in memory cache i have one custom field that i'm adding on here so if anywhere in my app i query for apollo auth data i'm returning the value of this reactive variable object that i had set right here um so there's that all right this one's important as well so refresh auth token if it is expired so this one does uh just just what it says so it can tell if the if the auth token is valid or undef undefined it then calls fetch access token and just call you know uses the browser's native fetch right here to try to get a new uh auth token and then if we're able to do that then it sets that so you can see i'm calling set auth data it actually uses the new token that came back and saves that so that ultimately gets updated here in this reactive variable right there as well as that use off hook that we saw previously in in both of those places since they ultimately behind the scenes they all reference this reactive variable here so in all those places the users are now logged in and we know who they are so for all this this also can refresh stuff i'm using a mpm package called apollo link token refresh so i just followed the documentation for that package and i was able to piece this all together and get json web token refreshes going out of my app this one's critical as well so as i mentioned we have to include the token in the headers of every request back to the server so i know who that person is so that's what this apollo link does here so i'm using apollo auth data to grab the data in that reactive variable and pull out the auth token and then you can see the headers i'm just spreading any existing headers that were already on this request i'm still including those but in addition i'm tacking on this authorization header and saying if we have an auth token then um you know use that for that header otherwise just make it an empty string so this one's important is how we're able to authenticate the user on every request in the first place what else do we have every time a request is successful and the response comes back the wp graphql jwt plugin let me pull that back up this that authenticates the user every time a request is successful it actually sends back new uh auth and refresh tokens as well so that's why i have for this apollo link right here i'm saying when a response is sent back from the headers get the new auth token here as well as the new refresh token and pull those off and then if i was able to get this one then great go ahead and save it if i was able to get the refresh token then great save that so that just extends uh the the life of those because there's a certain time limit or a certain expiration date that these tokens have and if you're refreshing them on every successful request you're just kind of bumping that time limit with every request so that you don't have to refresh them quite as often so this is kind of a nice little addition right here all right and last one i just have what gatsby calls or sorry what apollo client calls the terminating http link right here so this is the one that actually takes all the other all the other links after they've done their their work this terminating http link right here goes ahead and fires off that request so you can see i'm telling it for the uri also use that um wp graphql backend um that we had set use that same thing and then for fetch right here rather than using the browsers fetch i'm actually getting this from this npm module node fetch right here i'm doing that because then fetch can be used both at build time as well as on the client so when this app is being rendered at build time on netlify servers and encounters calls to fetch they you know work work without a problem so there's a lot there let's go back to where we started though so we started looking at pages and then index inside of layout here that's how we got it talking about all of the the auth stuff there's one last thing on authentication i wanted to show and that was this use auth token refresher so this this hook right here this just has a use effect that figures out if we should refresh the person's auth token and if so fires off a a request to do that and saves the new the new auth token so remember when we were going through that apollo js file i mentioned that we only persist two of these the refresh token in the user but this one was null i have i have that other hook this one the use auth token refresher it's the one that as soon as the app boots up and we see that we have a refresh token but we don't have an auth token it's it's the thing that actually triggers that request and then gets this thing set initially so i put a lot of work into like figuring out how to wire all this up but now i have um authentication working really well so you the user can sign in sign out sign up as a new user on the site they can request a password reset email to be sent to them and that gets sent to them and they can also uh click the link in the email to then perform a password reset all that stuff i have set up as well as this client-side stuff to do the token uh refreshing and so on so there's a lot there um so with that let's circle back then to our our front-end app so we started with the homepage here and then got into talking about all of our authentication stuff let's head back to the next tab in our app though so that'll be like the runs tab right here let's see what that looks like so inside of pages i'll go to runs so this one i have this styled page wrap with some styles and i have this content oh auth content is another one um related authentication uh this is a component i have that is very simple it just checks if the person's logged in and should have the ability to view this auth content then great show them um show them the children you know whatever is is nested inside of this component that's what they're allowed to see otherwise if they're not signed in show them the sign up page instead so i use this as as kind of a guard you know to wrap any content that requires authentication in the app so on the runs page i have my styles wrapper here i have my authentication my auth content wrapper here and then inside of that is the runs uh h1 a div and then the different sections of the page so so here's our runs total and then for this total mile section i have that here it's pretty short and sweet um i have a hook you use total miles that is used to calculate that it looks like this so it runs a um or the query looks like this so there's still the viewer the person who's currently logged in it gets the user fields and then the total mouse for that person and that's how it has the data that we need to display in that section and then i have a link to add the run that's my yellow button right here and then after that we have the runs list this is the one i mentioned has infinite scroll so the user goes down they see the gray area momentarily much faster on production as i said get rid of my local here but that infinite scroll is done in this runs list component so that looks like this um so here it is i'm using this infinite scroll library from npm right here react infinite scroller and that one i just tell it you know if we have any more to load right here then it knows if there are more pages exist i give it a function to call when we want to load more so it can go ahead and call that for the loader to use as a placeholder while we're you know working on loading in new ones i just have this like that grayed out little skeleton card that i'm passing in for that and then inside of all that i just have i just map over the runs and for each of my register this this run component here and that's how we get the card look just like that um so it might be up for that page let's see if there's anything else interesting here to check out uh could you just look at this get runs query i suppose that looks like this so when you actually fire off the query to get runs we're telling our back end like where where to start querying so that means if we fetch the first 10 then we would say give me the first 10 but don't start from the beginning instead get the first 10 after um the 10th one that we had gotten so get numbers 11 through 20 or whatever and we're also passing in the user id so it knows who to query for and then that data comes back we're saying do we have a next page if so what's the end cursor for that the last page on the current page and also for each of our runs we're getting all the data about that run here next let's check out some of the functionality for adding and removing runs so in our front end application we have the yellow button here add runs when the user clicks that they're presented with this form and when they fill it out we fire off a graphql request to save a new run post type for that person so let's take a look at that so if i go to the add run page right here take a glance at that so this alright so this is the first time we've seen this so we're getting some data that we fetched at build time here so our app gets this awards data and then it uses that to determine you know after a run has been added if the person has now earned the next badge or earned the next medal or if they have not yet earned it and then based on that it knows you know what messaging and what sounds to to show and play for the user um so let's take a look at that uh real briefly so if you're familiar with gatsby at all this should look pretty familiar if i go to gatsby node right here in this file we can take a look at what we're doing inside of create pages here so you see i have a few pages that actually care about this award data so i have templates for for each of them and i have this query called awards query that grabs all the data that we care about um for each of our awards so just as a reminder in our back end like award was this post type right here and then for each one of these we have these details that we want to query um so what i'm doing is rather than do do this at runtime in the browser when the user is using the app because these awards are the exact same for all users who visit the app i just do them ahead of time on the server at build time because our front end is a jam stack application and and i can fetch that data ahead of time so this is what the query for that looks like the uh wp gatsby plugin that provides this field called all wp award so you can query that and as its name suggests it gets you you know all of the posts within that post type so i'm querying for that i'm getting the database id aka the post id um the slug where it lives that's how i know how to build the pages so if somebody you know navigates to a page like this you can see i'm getting the slug i'm using that to build my urls like that for the award fields i'm getting the miles required to earn that particular award and then this is the award types taxonomy so i'm getting the slug for that so again back in our admin that would be this so the miles we saw a second ago and then the award type would be this one whether it's a badge or a metal so that's how i'm distinguishing which is which right there and then for our featured image i'm getting all this stuff the source url alt text and so on for our featured image i'm getting these some of these sizes so that i can make them responsive images so if the user is on a very small mobile device it'll just pull down a small version of the image otherwise if they're on a large device it'll get you know the high res version and so on so i run that query at build time and if there are any errors i you know uh throw the error there otherwise if there are not um i do a little light data processing here in this format and sort awards um code right here i'm just just making a few little tweaks to that so it's formatted the way i want for my frontend app and after that's done after i'm done making a few tweaks to that awards data i then go ahead and create the three pages so you can see my comments here create the add run page to create a page at that route i say for that page i want to render this particular component and here's the data to pass down to that page so that it has it ready to go so you can see i'm passing down the awards to that page likewise i'm creating the timeline page giving it a route component passing down awards and then lastly create individual award pages so for each individual award i'm saying what's the slug for this thing and then create a path that looks like this award slash whatever the slug is that's what we saw here award slash roman burns that's where that route comes from it's built right here and then same thing choosing you know which template to use for that and then passing down the awards data for that page um yeah so that's all the data that's fetched at build time and then pass down to a few of my pages uh so back to the add run page then so in our app if i navigate back to runs and then add run so this form right here let's take a look at what that looks like so here's the add run page and then inside of that the add run form and passing down that awards data that we had got at build time and passing that through to this add run form component here so let's take a look at that so this uh just some interesting stuff we'll take a look at all of this let's first go down to the markup though to see what we're actually rendering out so you can see i have this um styled form right here and then a form submit handler right there so take a look at that function and inside of that i'm just my date input right there my miles input right there and then just handling like if an error has occurred displaying that that nicely and then the submit button at the bottom so the text changes if we're currently loading uh the text on the button re-render to say adding dot dot dot or otherwise if we're not loading then it just says add so be this button down here so let's glance through this a bit so it's a pretty straightforward form right here and then on submit as i said i'm calling this handle submit function so let's take a look at that so here's handle submit what actually happens when you type stuff into these fields and then hit the add button um this is what gets triggered here i'm calling prevent default on that and i'm just firing off a create run graphql query i'm passing a few things through to it so i'm saying this client mutation id is just the unique identifiers required for all graphql mutations so not too much to to worry about there beyond that though i'm passing through the date and also the miles for this graphql query when we look at our wordpress backend we'll see how i'm doing this how i'm extending the schema that wp graphql provides to us for creating posts um of type run right here to not only include things that wordpress natively knows about like the title and um and who the author is or whatever but how i'm extending it to also have fields to add the miles and the date like we are like we're doing here so see how that's done in the back end when the user submits the form that's all i'm doing providing the defaults and then firing off the create run mutation so let's take a look at that i'll jump to create run and that looks like this so i'm i'm using this use mutation that i get from apollo client right there i'm calling that and then i'm passing into it this tagged template literal that i've built up here called create run so this thing create it takes the three things that we just saw a moment ago it takes the client mutation id the date the run took place the miles for that run and then using those three variables it fires off this query and then passes those each of those through you can see i'm um i'm setting them to to be published posts because the default is draft in wordpress so i'm saying instead when this post is created just go ahead and publish it right away when the result comes back after the runs created i'm getting some of its data i'm saying for that run i want the author node user fields and then total miles so this would be after that run has been added now give me that user's new total miles count back because using that i can determine whether or not they've earned the next badge or medal all right so some styles right here that that's what takes our regular form and just applies you know some styles to it to create a new styled uh form component there and let's see so let's finish talking about the submission stuff real quick so we talked about handle submit that's actually fires off create run once that response comes back i have this use effect that handles it so you can see it says handle successful run creation so this one says if we if we don't have any data yet just bail out you know let's use the fact i don't want it to do anything if that's the case otherwise if we make it past this check that means our query to the back end to create this run has actually completed and we have data available so i'm doing some um data processing here i'm saying for the data that came back and for the create run mutation get this value inside of it run author node user fields and then finally total miles um that would be that person's new total miles right there and have a little check has all right has user already earned likewise this one i can pass an award and that'll answer the question has the user not yet earned that badge so i do what i do then is take all the awards and i filter it twice i filter it once to say has the user not yet earned this badge and filter it again to say has the user or sorry um the first filter is has the user not already earned it and the second filter is has the user not yet earned it so what you get at the end of the day if you take all awards and you take away all those they've already earned and those they have not yet earned what you're left with is just those that they have earned this this time around for this particular request so that's how i do this check i say did the user earn award so if the awards earned filtering that we did if if that has a length at all if there's even one award that they earned in that array then we know that they did indeed earn it so then i'm doing different things depending on if they earned it or not so let's say they didn't let's skip this block and say they did not earn anything new so in that case i'm playing a mario coin sound i'm adding a toast notification where i have a random success message and then i'm programmatically navigating them back to the the main runs list page so there's some fun stuff here i think so let's first take a look at the play mario sound right here so this comes from this hook i created called use sounds so this is pretty fun in my opinion so i'll go to the use sounds uh hook and take a look at that so in my project i have a hooks directory right here and then a used sounds custom hook that i built and this uses a library by josh como uh called use sound which is pretty cool uh as the name suggests you can just give it some like mp3 files and then anywhere in your react app you can trigger those um sound effects to happen uh obviously on the web you want to be like tasteful about this you don't want like you know auto playing uh sound the user didn't ask for as soon as the page loads that kind of thing um but these are for like little tasteful sound effects here and there in the application so i decided this library is a ton of fun and i wanted to use it for my project so that's where use sound comes from and you can see i have a just a few sounds in my project so if i go to here the sounds folder i have a couple mp3s i have applause fanfare the mario coin sound and then pop down another one so for this i'm creating some react context and then creating a sounds provider i'm calling the use sounds hook and i'm passing into it you know all four of those sounds and then using those four functions i just pass all four of those down as the value for my sounds context provider right here and then lastly i just have um a used sounds hook so if i go to get guess we browser here um you'll see all the providers i'm wrapping my app with so one of these lets the app know about the current network status if we're online or offline one of them is my apollo provider so that anywhere inside of my app i'm able to run graphql queries and mutations the auth provider that passes down you know whether or not the user is authenticated so i pass that down this is for toast notifications which we'll see next and this is that sounds provider that we just looked at a moment ago so this allows me to play any of those sounds let's talk now about if they had earned an award though so i play two sounds at the same time so you can see right after the other and playing applause which is just like a crowd you know clapping that kind of noise uh and then fanfare somewhat similar but there's like a little trumpet sound edition in addition to to that so i play both of those at the same time just for like some cheering a little sound effect i just play a toast notification says you won a new award congratulations like that and then i'm navigating them somewhere different so award to to navigate to is this one so out of all the the awards they've earned you know figure out what the last one is another thing i think that would be interesting to see is this use refetch queries i decided to stick this functionality in a hook because i would need it in multiple places so like if the user adds a run right here then you need to do a few things i need to um i need to you know refresh and re-render the total miles i need to refresh and re-render the list of runs right here because it'll show the their new one at the top and i also need to refresh some leaderboard data so like um whichever uh whichever these groups they're a part of as well as their age group right here those need to be refetched as well whenever a change happens likewise all of those different things would also need to be refreshed if the user deletes a run like that so i decided having all of those that query refetching to have all of that in a single like hook that i can easily call from anywhere in the app would be really helpful uh so as i did i created this use refetch queries so let's take a look at that real quick and you can see what this does so this um calls our use off hook to figure out who is this this user and it grabs their id and then you can see it returns this array of things that we need to refetch so we're saying our total miles query apollo client you're going to need to refetch that now that changes have been made we need to refresh the user's list of runs now that they've added a new run so you'll need to um refetch this query as well you'll need to refresh the leaderboard counts for the user's age group and then the leaderboard counts for the user sex as well so those four queries right there and it returns an array of all four of those so if we head back here we can see how that's used so i'm um calling use refresh queries and getting that big array of queries to refetch and then passing that right in um to apollo here so that makes it really easy like i said in other places in the app where i need to refetch all four of those same queries i can do the same thing i can just pop open some curly braces and then pass that in and call it a day so that's about it for the the add run form let's take a look at deletion real quick uh so that would be again like my cards that i have right here if somebody hits the x then they get a toast to confirm that change uh so let's do that we'll start off on the the runs page so again just as a reminder the runs page has this runs list so click on that to jump to it and the runs list looks like this we've seen this before there's our infinite scroll component but we haven't looked at this though so the individual run cards like this let's click through to those so run just looks like this so what are we doing in this file so we have some styles going on and we're pulling in miles and then the run delete button so that's the x that they need to click and they're rendering um each one is one of those is a list item like like that or i'm just displaying it's like date and miles right there so here's the delete run run button um that gets rendered right there so we take a look at that real quick so this delete run button looks like this um we are saying so we're saying use mutation and then we have this delete run mutation that looks like this and i didn't do any customization here this is the delete mutation that wp graphql provides whenever you activate wp graphql on your site you get some mutations for free for some of your post types including one to delete you know uh those post types so i'm calling delete run i'm just passing in like the appropriate id for the um for the post to be deleted all right so i'm saying uh that's the query i want to use and then refetch queries we talked about them that a moment ago so whenever you you know make this change here are the queries that you need to refetch in the app just to make sure all that data is fresh and then uh i'm calling that function to perform the mutation delete run right there so you can see here i have just a button and then on click i have this handle x click which is uh this this function here so that would be this button if they click that then it call it calls handle x click which is this that's a div that says delete run question mark with a button to handle uh with a function called handle confirmation click right here and i'm calling add single toast as you can see which makes it display as a toast notification so when they click that then they get my markup displaying inside of the button and let's trace this now if they if they are if they do click that delete button to confirm they do indeed want to delete that run that then calls this function so we're doing a few things we're saying we're displaying a toast notification saying deleting run so they know we're working on it we're firing off that delete run query or sorry mutation rather that we saw a moment ago and we're passing into it the info it needs to delete that particular one and saying then when that resolves then display a toast notification that says run deleted so again to see that again our 50 miler will remove so if i go to delete and then i can confirm this one i get deleting and then ultimately run deleted like that and then you can see everything has refreshed that should be refreshed because of this because we're calling our refetch queries there let's check out that timeline for this page i was able to actually find a timeline react component that somebody used an open source that was perfect i was able to just drop it into my project so let's take a look at that so inside of templates i have timeline right here so this one again it takes that um that data that we fetched at build time on netlify servers and and that gets passed down into this timeline page we're taking that and destructuring the awards off of it and passing those through to this timeline component right here as a prop so our timeline component looks like this so at the top you can see i'm using this react vertical timeline component i was able to find this on npm in this library it worked out really well for my project so here's our timeline component uh i'm calling the use totalmiles hook to grab a few things there and see if we're loading or an error display one of those otherwise if we have the data that we need to work with go ahead and render the rest of the page right here so you can see i'm using from npm that vertical timeline react component that i'm given i'm using that and i'm mapping over each of my awards here and displaying this and now admittedly this like i could have broken this out probably into another component right here for the individual wards but this works just as well so for each award i'm pulling some data off of that i'm restructuring the award to pull off this stuff figuring out if it's a medal right here versus if it's a badge and then if the person has earned it and then i'm displaying each item in my timeline like this so you can see i'm saying if there is a featured image then then we're going to display this and if the user has also earned it then display the actual you know link to that award page and make it and display the responsive um image right there so that they can click through they can click on this and actually go to the single page and because they've earned it you know we're also going to go ahead and display the title for that thing as well as the content for that thing all right let's say that's not the case though if the user has not earned it then we would do the other case right here which is we'd render a responsive image um but we'd apply a class of not earn so that uses some css like filtering stuff to give it that kind of teaser grayed out look like like this so you can kind of make out the shape but not actually see you can see some of the like um definition some of the lines poking through right there a little bit so you get that little teaser and then they just get a bunch of question marks and not yet earned for the for the title right there and for the paragraph tag um so this timeline component you know will re-render if any of this stuff changed if the user has now earned a badge that they hadn't earned before or whatever all this would be re-rendered and it would show the details for that particular award rather than the mysterious question marks and not yet earned and grayed out image and stuff like that um the last tab we have in the app is the leaderboard right here so this is the one where um it displays all of the totals uh that all of our athletes you know have racked up and you can find your own name and and see you know where you rank um and try to work your way you know up up through the ranks and be you know the the person who's uh who's leading the group so let's take a look at that now all right so i'm saying um so i'm capturing in state uh what the what the age group that they're currently viewing is something you know my age group or all female or or all male so that just keeps track of this state here for which you know one of one of these they have currently selected and then based on that that view you know which one of those three it is then we pass down that info into a few of these different components so the keyboard or the leaderboard switcher which is this component here we pass in you know what all of the views are what the current view is and then also the mechanism to update that to set the view right here so you can see that that's a pretty simple you know set of set of radio buttons right there they're styled to look like kind of a horizontal switcher component right there and then when the user clicks any of those um we just you know call that the handle change function right there which um plays a little pop-down sound and it also performs the change so called set set view and sets it to that new thing and we also have uh the leaderboard it's itself right there is what this component looks like so you can see what we're doing here i'm using use auth and finding out who the current user is and then doing a little light data processing here i'm saying is this the my age group view figuring that out and then if it is use the user's sex otherwise use you know whatever viewer on and likewise for age group if the user viewing their own use their age group otherwise set that to null because we're going to get all age groups like for these tabs over here we're not going to limit it to a certain age group so once i do that then i'm calling this use leaderboard function i'm passing in those details right there and i'm getting the data that apollo client gives me back and also my loading and error states and this used leaderboard is right here so we can check out this this hook so it looks like this uh you can see i am using apollo's use query and passing in the get leaderboard query right there and passing through to it you know the details about like the data i want to fetch and yeah there are the variables being passed through i'm saying if we have data then great um do some light you know formatting of that data to get into into the format i want and set that as the leaderboard and then return that if the data did come back but there's no length meaning no one has logged any runs then i just display a message like no results yet get out there and run some miles like that otherwise if we make it to this point and we actually have you know users to display on the list then i'm taking that leaderboard data and mapping over it and saying for each age group pull off of that what is this age group its label and all of its runners inside of it so that would be like this age group um and then the label for that and then all the runners inside of it and then you can see i'm looping over each of the runners and saying you know render this leaderboard row that'd be each individual row like that so you can glance at that real quick so there's a component that looks like this miles looks like i made i made that its own components and then i have some styles that get applied to each list item like that and then here's the component so i'm taking the props passed in and just destructuring those to pull off the person's user id or or database id right there first name last name and then some of their acf fields so their city state and then their total miles right there and it's just playing a styled list item right there with that person's first and last name concatenated so just like that and then um their their city and state below that kind of with the grade all caps text right there and then lastly they're miles so i think that's about it in terms of the functionality for this front end application hopefully you thought that was cool getting a little tour of the front end gatsby and react app for this next we'll take a look at the wordpress back end though and see how this is wired up there so here i have the back end loaded up on my local and let's just just as a reminder let's take a look at the plugins that we have in place here so advanced custom fields wpsp wp graphql and also the gwt authen text query plugins that go along with wp graphql and wp graphql for advanced custom fields that adds the acf fields to the graphql schema so that you're able to query for those from your farnet app so that's what we're using we'll dive into this though the the run through history core plug-in this is where all the bespoke you know custom functionality for this app exists so we'll look into that um even before we do that though let's uh talk about the theme real quick so the question may you know occur to you what theme do you even use for a headless wordpress app um because we're not using the front end to display anything and in fact if you try to visit the front end here for my app you'll get redirected to you know the the actual headless front end so i'll show you just just the simple kind of placeholder theme i have here called headless wordpress and i'll dive into that custom plugin so here in the back end code if i go to wp content themes i have this headless wp theme right here and this is a simple uh placeholder theme basically that i wrote so it's um it's up on github like if you're interested in using this for any of your projects it exists here so it's on my github just called headless wordpress theme right here in the readme just tells you like how to how to use it that basically has has this as a blank style.css just with the headers that wordpress requires right there um the screenshot this is the wordpress logo uh like that this readme is what i um showed a moment ago on the github repo that just you know tells you kind of how to use this headless wordpress placeholder theme if you want to use it for yourself and then there's two other files to it so pretty darn bare bones so if you don't do anything else if all you do is activate this theme then it'll use this index.php for every single page requests for for the front end so just display a link right here to to the wordpress admin and actually i think i've updated the the repo to um rather than just display a link i'm using uh i'm displaying a proper like html page right here and then um functions.php does something a bit different so if you choose to display a front-end app url or define a front-end app url constant somewhere in your project and we know what the front-end url is for like your headless app in that case then when the template redirect hook is run by wordpress then we'll call wp redirect and send the user to that frontend url so that's kind of a choice you you have to make if you use this little placeholder theme you can either choose not to define that constant and just let you know this this render out so user just sees a link um to the admin or otherwise if you choose to define that constant then it'll just automatically be redirected to your front end app instead so you can do kind of one of those two behaviors whatever you think is is better for your application so that's what i have uh just for my you know placeholder theme uh to satisfy wordpress and get something in place for that let's dive into plugins though so here's the list of plugins and the one i want to focus on is run through history right here so my plugin headers at the top and then a single um add action that kicks everything off so this is a simple bootstrap file uh right here that gets everything running and then all the um functionality for the plugin is inside of the source directory so everything here is inside of this um plugins loaded callback function right here and it uh grabs the composer autoload files from inside of the vendor directory right there and you can also define a array of dependencies here uh for this plugin and it'll you know check to see if um all of those exist before loading up the plugin so you can say here are my dependencies i need the composer autoload files and i need wp graphql and if you have any other you know plugins that are critical to your project that this plugin depends on you can define those there if you want i'm using a few array methods here to just to determine which dependencies are missing from that list if any and then for saying if dependencies are missing display an admin notice in wordpress and return early um so you can see i'm you know i'm saying if dependencies are missing then call add action and hook to admin notices with this function which is defined above here so it has access to the missing dependencies and it displays this admin notice in the wordpress admin so let me show you what that looks like real quick that looks like this so my project depends on wp graphql so if i deactivate that for instance i'll get this so a few other plugins are doing similar things as you can see similar checks but this is the one that our plugin is generating so the run through history core plugin can't be loaded because these dependencies are missing wp graphql it's just bailing early because of this return statement right here so all it's doing at this point is just rendering the admin notice message and then returning early it's not actually loading up any other any other code just because it we depend on having wp graphql active so let's activate that again and our messages should go away so there we go um so if we make it past that check then if we're not missing dependencies then we'll do this require once all of our composer auto load files for our project and then we'll instantiate this main run through history class which is inside of our source directory and that's kind of the main class for the whole application so before we dive into that though um i'll just mention that this plug-in boilerplate is also on my my github in case you're interested in that at all so if i go to my own github go to repositories and we'll go to yeah so it's wordpress plugin boilerplate so that's what this is uh it uses a psr4 autoloader displays the wordpress admin notice and doesn't run when dependencies are missing so that's the thing that we just saw a moment ago provides a hookable interface that all classes with action or filter hooks can implement so we'll see that in a moment and then just has the steps to use like clone it down and just do a few you know search and replaces of a few um a few string literals right there and then you know you'd be up and running to use this plug-in boilerplate yourself if you'd like to as well um so back to the plugin here let's say we're not missing dependencies and we're going to go ahead and require all the files php files for this project as well as instantiate the main run through history class so let's take a look at what that looks like now if i go to that file it looks like this so so i'm saying this is our main name space we're calling run through history and here's the final class for run through history so we're doing a few things here so we're setting up an empty uh instances array right there as a class property and then here's that run um method that we saw a moment ago so the main bootstrap file it calls an instantiate run through the street and then calls that run method so when that is called this is what executes it first calls create instances and then register hooks so we'll take a look at those real quick so create instances does this it just instantiates any of the classes that i want to be instantiated from inside these directories here and then adds them to the instances array right there so after this code executes what we're left with is that instances array that has keys with these names and then the values for each of those are all of the instances for each of our classes right here and i'm do doing some dependency injection here as well so like this roles registrar registrar class it actually needs to know about it needs to have access to some of these other instances on the list so what i do is i just pass all of the instances into roles registrar so then it can just do a quick like filter to um to find like the instances that uh that it needs you know access to and then perform whatever functionality it needs to i'm also passing it into this user register class right here all right so after that happens we end up with like i said our instances array right here with all of the class instances inside of it so once that's done then we finally call register hooks so that's this function it calls this get hookable instances and loops over each of those and calls a register hook method um all right and how you might may ask yourself like well how do i know every single one of these instances that i'm getting here has a method you know with this name that we can just straight up loop over them and call every single one and that's because they because i'm applying this array filter right here and that is taking all the instances and filtering them to pull out only the ones that are an instance of interfaces hookable so that's let's take a look at that so if i go to in my project interfaces and then hookable this is a simple interface that just says you know any uh class that implements this has register has a method called register hooks and that method is for registering hooks with wordpress so that's what happens here taking all the instances filtering out only the ones that are hookable and then returning that value so then once i have only the hookable ones i'm looping over those and calling register hooks so then any of these classes i instantiated right here any of them that are hookable that have wordpress hooks to be registered i'm going going ahead and doing that and uh and calling register hooks right there that's kind of how the plug-in bootstrapping and then also the main plug-in class uh workout here uh from here i thought we could kind of go through some of these folders and you know pick off uh what might be interesting details so we can look at we'll start simple just go to the admin folder right here so i have a class called admin menu customizer so here's one of those register hooks that i mentioned and this class implements hookable so that we know you know it has some hooks that need to be um registered and then we go ahead and call add action so when the admin menu hook fires i'm calling customize admin sidebar and then removing posts pages and then comments just to clean things up a little bit just because i didn't you know we're not using those uh post types for this project we're only using some of our custom ones like awards and runs so i'm just doing a little like cleanup of the admin sidebar right there i also have this admin access class right here so this is another hookable class it hooks to admin init and calls this log out and redirect non-admins function so i have some some users on the site are admins like myself when i log in others on the site have a user role of runner so for those folks like i don't want them to ever be able to access the wordpress backend as you've seen i have all the authentication stuff that they might need to do is all in the front end app so they can manage their account from there they have no reason to log into the wordpress backend so let's have this little check to make sure that that's the case so i'm saying if you know this this is an admin user then do nothing you let wordpress function as it usually does and allow the person to log in otherwise if we make it past this check we know that this person has a different user role they're not an admin uh so if that ever happened if the user is trying to log in i just logged them out immediately and then send them to the front end app so it says if a frontend app url is set send the non-admin user there otherwise send them back to the admin login page and here again is that front-end app url that we saw once before supposed to be um make sure you wear that so if i go to my wp config file right here where my constants are defined that would be here for an app url just like that yeah that's it for the the admin folder let's take a look at some of these other ones so email this one is worth looking at uh because i had to do some some custom things to to allow wordpress to work in a headless environment for doing the password resets and stuff like that so we'll take a look at that so email settings so right here i'm filtering a few things i'm saying on the uh wp email from filter do this instead so i'm saying um so by default wordpress does when it sends emails it sends them it from wordpress at whatever your domain is so i'm just customizing that and saying info at my site name instead uh just for just because i like that naming a bit better i'm also customizing the from address let's see so that was the from address there and then the front from name uh would be here so so i'm doing something similar i'm just saying you know when they um when the email gets sent instead of saying it was from wordpress instead grab the name of of our um of our wordpress site which is run through history and then you know call function to make sure we're decoding that and it's in the right format so when emails are sent it'll say it's from run through history instead just to customize that a bit further i'm also yeah so these remaining ones are all related to doing password resets and auth stuff so when user retrieve password title on that hook i'm calling modify email reset subject all right so new user is performing a password reset i'm saying you know grab the site name which is run through history and then append to that dash password reset so when the email is sent that's the subject line here's the retrieve password message now i'm also custom customizing that so for that um i'm displaying this message so a password reset has been requested for the following site name account so for me that would be run through history account and it just displays you know the user's email right there if you did not request you can safely ignore and then it says you can reset your password here and it um call this function and pass in the the special uh unique key that the user is going to um going to need to have to reset the password and also the person's login and then this little helper function here that takes you know their login url encodes that grabs our frontend app url so that it knows what that is and then builds the url here so this is a key part of the app when the user goes to reset their password they go to check their email this is the link they'll be included in their email so it'll be you know whatever the front end app um url is and then slash set password and then we have some url query string parameters here we have the key equals the unique key that they need to identify or to perform the reset and also whatever their user login is that's how we build the whole url right there and tack on an additional parameter if it's a new account and ultimately return that url so when the user requests a password reset this is the language that i'm choosing to send you know to to the front end and then when the url is presented there i have my custom url being set and this is really important if you if i didn't have this in in place then when the user requested a password reset wordpress would send the link the url that it usually does which you know would send the user to where the wordpress site lived it would send them to your wordpress backend to perform the reset there uh so in my case i didn't want that i want everything all user interactions i want going through you know and being managed inside of my front end app right here so i think we've been through most of these except the last one here so modify new user notification email let me see what we're doing for that um yeah so i just customized this one so instead of the standard message message that wordpress sends with um with the url to where the wordpress site lives instead i'm sending a custom message with the url to my you know front-end decoupled front-end application so email stuff super important for a lot of the authentication related stuff like i said uh new user signups and also password resets uh skip interfaces for now we'll touch on that later let's check out media right here so featured images not too much there i'm just hooking to after setup theme and adding support for featured images and that's because for my awards post type as we've seen i'm using the featured images right here to display those here's my post type so i have um two of them i have the run post type and then award post type so you can look at each of these on init i'm calling this register method right here and calling register post type and um just doing the usual wordpressy stuff saying i'm i'm supporting the title and author in the case of the run post type here and these are really important here so if you read the wp graphql documentation you'll see that for any post custom post type that you want to expose in the graphql schema you need to do this you need to say shown graphql true and you need to tell it what the single and plural names should should be when it gets added to the graphql schema so you can see for me i'm saying that for the single name use this self graphql single name just like that so it's capital r run and for the plural name use runs right there so you might have noticed that i'm using some class constants right here to define the key for this custom post type as well as the graphql single name and the reason i'm doing that is so that i can easily reference these in other places in my app um so that i'm not like hard coding them in a thousand different places um and and making it so that if there's a typo in even one of those places my app would break or if they didn't match up and all those places my app would break this so i just have a single source of truth so what i mean by that is like anywhere in my app where i need to access the run uh post type i can just do like the run posted class colon colon key just like that all right so the run post type award post type is very similar uh there's registered post type does similar stuff and then i'm also you know using the class constants for the key and graphql single name i do have this post type label utility though so you might have noticed the use statement i have at the top of both of these post type classes i'm i have a trait called post type label utility so that's in the same folder right here so this is a php trait right here that just has one protected function called generate labels this is just a helper method you can say you can pass into it whatever the single singular label is whatever the plural label label is and then any additional ones that you might want to tack on in addition to this list and then it'll return to you this pre-generated you know list of of all the labels they'll just build those for you so any you know any post types that want to be able to generate labels for them they can just you know call use post type label utility and as long as they've done that then it's safe for them for the labels to go ahead and call this arrow generate labels and just pass in a singular and plural and then call it a day so that's it for my post types let's take a look at taxonomies on the site so award type taxonomy that one we've talked about so for my award post type i want to be able to distinguish what's a badge from which what's a metal and for this one i have a few class constants as well so i'm saying the key for this thing is award underscore type and for each of the terms i'm actually hard coding these so i'm saying the badge term is badge the metal term is metal so my two terms like that i'm just hard coding what those need to be in these class constants and then i'm going to make sure they exist actually as soon as my app boots up for the first time so i'm hooking into init and i'm saying i want to call this register function right here which calls register taxonomy and then um similar to my post types we have a generate labels trait we have this trait here with the generate labels utility on it so we can call that to generate our labels and that gets included right there inside of this class so we have that and then we're passing through we're also saying we want to expose this um taxonomy and graphql and then we're giving it what the single name and plural names should be right there so that's what actually renders or that's what actually registers our taxonomy right here and you notice i'm also hooking into admin init and then saying ensure terms exist so for this one i'm saying um as soon as the wordpress admin boots up for the first time ever like if this uh plugin has just been activated for the very first time and we're just loading up the admin on the next page load after plug-in activation then this will run it'll call wp count terms and pass to it the key for this taxonomy and it'll um you know grab get the number of terms i'm saying if the term isn't two if if it's anything else then go ahead and call create terms and that just calls wp insert term to insert badge and metal and then for each of them it uses those hard-coded you know strings right right here so badge and metal so it just goes it goes ahead and creates those i wanted to do that just to make sure these exist 100 of the time so then you know if i ever reuse this code base anywhere else and um the the decoupled friend app is trying to query for the badge and metal terms or whatever they just always exist i have that guarantee that you know those two terms will always be there so that looks like um all right after that i have a few user taxonomies uh i mentioned once earlier in the video that user taxonomies like wordpress from a technical standpoint does support them but they're not really like first-class citizens if you register a user taxonomy you won't get any screens in the wordpress admin for managing those and it won't even recalculate uh the term count and things like that for these so so bear that in mind as we're looking through um some of this stuff so i have this so the age group taxonomy right here um so a few traits that i'm pulling in here so i have a taxonomy label utility which we've seen once already i have user taxonomy count updater and the user taxonomy term updater as well so these are things that wordpress doesn't do for you for user taxonomies all you have to do yourself so we'll take a look at those in a moment so for this user age group taxonomy i'm doing a few i'm registering a few hooks here so i'm hooking to the init hook here and i'm saying that the callback function is register right here so it calls register taxonomy and then passes in some pretty standard stuff right here i'm using that generate labels helper method that my you know utility class provides the taxonomy label utility one that we talked about before let's see just like with our post types these are of course important i'm saying yes expose it in the graphql schema providing this plural and single name right there next one is ensure terms exist so similar with a few of our others i'm just saying the very first time this app loads up when admin init you know first fires go ahead and call this function i'm just saying if there aren't eight terms already go ahead and create all of them so here they are you can see i have just the human readable text right there and then for each of those i'm just saying for this taxonomy right here here's the slug and i'm passing in um just these constants right here so let's uh create terms and then after that is render profile fields right here so this again is something that wordpress doesn't do for you for user taxonomy so you have to um kind of roll your own so that would be here in the wordpress admin if i go to my own profile page and then i scroll down we have the um the age group and then the uh sex taxonomies right here with radio buttons displayed so that's what this does is just grabs you know what the current terms are for for each of those and then displays you know whatever the heading is if there are no terms you get a message saying there are none otherwise if we do have terms it goes ahead and just loops over each of those and displays the list of radio buttons and for each one of those that you know we actually do have the term set for this particular user it just shows it as you know the selected item right there as being checked uh so that's it for user age group taxonomy um although if you've been counting we haven't accounted for all of our callbacks actually so this saved terms right here if you look in this in this class you won't find it and that's because save terms is coming from this one user taxonomy term updater and then we have count updater as well so we can take a look at these so count updater um just looks like this so we have this update term count function right there i can find where that is yeah so i'm saying for this particular taxonomy when a term is either added or removed here is the custom callback function that we want to run to recalculate those terms so i'm passing in that function so that just does some work to recalculate what the count is for that taxonomy and then the other one is that save terms is actually hooked to i would be this one user taxonomy term updater so this is another trait right here that's used in uh a few places in the app so this one just provides a single save terms method right here so it um receives you know what the user id is and then ultimately is responsible for saving that for the user sex taxonomy it's really the same as age group you can see i'm doing exactly the same things i'm just registering what it is i'm saying the terms that exist are up here i'm rendering the profile fields that exist and then calling the cert the same exact save terms method we can keep rolling here the next one i have is user meta setters so talk about those here before we do that though let's take a quick break to look at our acf custom fields so i have total miles date of birth and then city and state so that would be just like we saw on the profile page that would be these these four fields right here if you recall from earlier i said that the total miles i recalculate this every time there's a change so every time a run is either added or removed by the user we go ahead and sweep through all their runs and then recalculate the total and reset this so that it's always you know ready for us to pull that that total value for the user and it's always kept up to date so that's what the the setters are are about right here so i have age group setter and then total mile setter so we'll talk about total miles first since that's what i just mentioned um so this class gets uh it's it's a hookable class so it gets you know automatically um the the register hooks method here gets called when the app boots up and registers a few um a few hooks right here so i'm looking to user register so i'm saying when the a user is first registered like if they're signing up to be a new user for our app then initialize total miles so i'm saying you know for this particular user and for the total miles um meta key initialize that to zero just because just so i know that it's set to you know an integer as soon as they um become a user on the site like that and then i'm doing a few other things so i'm saying on save post call set user total miles and also on deleted post same thing call set user total miles so you can take a look at that here so it says recalculate and set a user's total miles whenever they create update or delete a run so this one you can see it just receives um whatever the post id is for the changed run uh if the post that was passed in isn't a run um i'm bailing early otherwise i'm continuing here um and you may ask like yeah let's just uh keep rolling here um so if it's not a run i'm bailing out here as i said otherwise if we make it past this check then we know we're dealing with a run post here so i'm saying you know git post field post author to find out you know who submitted this run and then i'm calling this recalculate method right here whatever it comes back with is the person's new total and that's what i'm using to call update user meta and reset um this value right here to that person's new total next one is age group setter so i mentioned once before that um the user doesn't actually set this age group we just set it for them when we know once we know their date of birth and they're registering for the site we just figure out what the age group should be and select that for them so that's what this class is responsible for so i'm looking to profile update and then calling this set method right here and you can kind of see what it uh is meant to do so it just calls um git user meta and it gets the person's date of birth you can see here that i just have like a series of ifs so i'm saying if the age we calculated is less than 10 then set the person's term to the you know term 0 to 10 for the age group taxonomy uh and then bail since we're done doing our work otherwise if it's this if it's below 20 then set it to 10 to 20. otherwise 30 do that 40 do that and so on user rolls i don't think we've looked at this so i have one custom roll right here and then i have a roles registrar class that handles both removing wordpress's built-in roles as well as registering the one custom one so this might be somewhat interesting to look at here um let me see what runner rule so run a roll is pretty darn simple you can see it just implements this user role interface that we have right here and that interface just says that you know you have to have this method called register and that is used to register that particular user role so because we implement that we know that this this class has it has to have a public method with that name and then when it's called i'm calling wordpress's add role function right there and i'm saying here's the name of it and defining that as a class constant right here um it's giving it a human readable name and then define defining what uh capabilities that that user role should have so that's it for runner roll like i said though we'll head back to rolls registrar and this does a bunch of things it not only you know calls run or roll to to register this but also um removes the built-in roles like i said so take a quick look at this does a few things so before we even get to register hooks down here let's just take a look at a few things so in the constructor right here it receives all of the instances for our whole um wordpress you know custom plugin here for our run through history application it receives all of the instances right here and you can say see that it runs an array filter and it filters out only the ones that are instance of user role so for us that would only be the user role right here this is the only one for our project i have other projects though where i use a similar pattern to this and they might have you know four five six whatever user roles so this filter works great to pull out just the instances that we care about that are the ones representing new user roles so we do that and then we just set this class property right here roles to you know the ones that we know we're going to need to register and then register hooks is called when our app boots up so i'm hooking to the init hook and saying i want to call this maybe register roles function right here so it does a check it says if should register should roles be set then proceed with doing all this stuff here so we don't want to do this um on every single request made to the server just because that would be you know expensive and there's no reason to do this if things have changed uh in if you look at wordpress documentation on setting user roles you'll often see like people suggesting doing it on plug-in activation you know that's when you register roles and set new capabilities and stuff like that i'm not crazy about that though just because like if if the plug-in is ever if that activation hook is isn't run like for some reason um then the capabilities aren't registered or if you make any changes like let's say the plugin has been activated for a while and now you make a tweak to some of the capabilities that your activation hook isn't going to run again you would have to like deactivate and then reactivate the plugin just for those two to go live so i have a system here that i use instead where i say i just hard code a version number here so it says custom roles version number bump this number if you have made any permissions changes and need the roles to be re-registered so i know that in my app if i've ever you know if i ever go into like the user role right here and i tweak something i add like any kind of permissions make any any change whatsoever to one of my user roles i know all right i can just pop into this file and you know increment this this to two and that's it and then just commit my change knowing that you know the next time this check runs should roles be uh set and that'll evaluate to true so that just looks like this i'm saying you know get this option from the database rth custom roles version right there so i pulled out of the database and just compare it with you know the hard-coded version in this class i'm saying if the one we have in the database is less than the one in this class then we'll know oh it's been incremented we better you know reset our roles we better um do all the stuff inside of this if block so when that's the case we proceed with removing the roles so you can see i call removal editor author contributor subscriber so just those built-in wordpress roles i don't use for this app i leave admin though the administrator role because i do use that for myself as well as you know my friends who run the race company they all of us are admin so i do use that but remove these other ones after that's done i call register roles right here so that one loops over all of the roles that need to be set and for each of those it calls the register method and remember i know that all of those have a method called register because all of them um implement that interface called user role that we saw right over here so i do that to get get all of them registered which for this app is just the runner roll and the very last thing is to call update option and it updates this rts rth custom roles version and sets it to be equal with the current you know version that we've hard coded in this class so the next time we run um this check right here uh the two will be equal so no we'll know that no changes have been made and can just bail out you know early as soon as this check is made right here um so let's have my roles system working um like i said i think it's a good setup because then you can just make any changes while your app is already already live and just you know bump this number knowing that wordpress will re-register things the very next you know request that comes in uh in this folder we'll play we'll pay particular attention to this wp graphql folder because this is where we're actually making some customizations to the graphql schema to make it uh work the way it needs to for our app so we can take a little tour through some of these so let's see what do you need to look at first um how about connections maybe so for runs yeah this would be a good one so for runs right here um you can see i'm hooking into uh this hook that wp graphql provides called graphql post object connection query args right there and i'm calling modify query args so this one i'm just making sure we're targeting the right field so i'm saying if runs isn't you know the the field name right here then return those query args unchanged uh if if order by is already set then leave it as is return that and otherwise i'm just saying whenever a runs query is is sent i want to order by the date the run occurred rather than the published date um doing something like like this where you're like changing the way the order in which things um things are queried across the board like 100 of the time you would just want to be careful with this you know because your app you know you may want to order it differently um you know in some cases or whatever for my app though like i was fine doing this so i just said like if they haven't specified an order just always set it to this so that we get the runs listed in the date uh according to the date that they actually um ran that run now let's take a look at users right here so this one i'm hooking into the wp graphql graphql register types hook right here and calling register where input fields so this is crucial this means when we're querying for users we can have it know about some additional input fields we can say you know we want the sex to be either limited to like male or female or we want the age group to be limited to you know whatever the current user's age group is if we want to like drill down to that so when we're running our queries we now have new where arguments that we can include in the query to to limit the results to those two things and then the next method here is where we actually make use of those so i'm hooking into this map input fields to wp user query so this is like before wp graphql runs a user query i'm modifying the the query fields first before that gets run so you can see what i'm doing here i'm saying if no term slugs are provided they just return the query args unchanged right there otherwise if they you know uh if we are querying based on a where one of these where arguments then proceed to actually use those but basically that one limits um our query to just certain certain user ids right there and they were saying uh if there isn't if we if we don't have include then go ahead and um that means we're including no users at all like if we queried for a certain age group and no one has registered in the age group at all then it's telling wordpress wordpress to include users who have a user id of zero which if you're familiar with wordpress it starts you know counting at one so this means i'm guaranteeing like we'll get no results back just by passing in zero right there otherwise i'm saying if users were found then limit the results to those users so i'm saying before our wp user query is run in the database add an include uh argument to it and then set that to the user ids here that we that we got back the ones that we want to limit it to right there and ultimately return our query args so that's how i wired this up um this could be done differently like instead of you know doing um doing some work here to figure out what the user ids are and then running adobe query to to limit limit those like that uh you could instead do a custom database query where you're you know querying for this data all all at once using a custom wpdb query for instance uh in my test though like doing it like this where i grab the ids pass them in here and then quickly run the wp query it was super super fast so i think this is kind of a cleaner way of doing it rather than messing around with custom queries if i don't have to so that's how i got the filtering to work based on you know the users like sex and age group when we're only querying for for certain you know groups of users like that let's talk mutations here so this will be pretty cool uh so talk create run right here so when i expose the run post type in wp graphql i do get a mutation called create run but i needed to do to save some extra data that uh that wordpress doesn't natively know about so i'm calling graphql register types and similar to what we saw in one of the other classes i'm registering some new input fields so here i'm saying for that particular field i'm going to register some graphql fields for that those are going to be the run date right here and then miles so after those input fields exist i can go ahead and use those in the next two methods so i'm hooking into this before resolve field and doing a little server-side validation here so with this validate method i'm just making sure we're targeting you know the right query here we would run this check we'd say if not is run date valid then uh throw an error right here so the user we would fail gracefully on our front end react app and just display a red you know error notice saying oh the run date is invalid sorry um we try our hardest to like catch that on the client side so that they don't submit an invalid date in the first place but just in case i have some server side checking right here as well to make sure it's valid then we would proceed to this last one here so i'm saying when a post object mutation is happening i'm calling this update additional data hook here and passing or i'm hooking to it my save additional data method so that's the last one in this file it and make sure this is the create run mutation as long as it is we do some data processing right here i'm ultimately saving two post meta fields so i'm grabbing our sanitized and formatted metadate and saving that as post-meta and then our sanitized miles as well that were passed in and saving that as as post-meta for the miles and then lastly i'm just calling wp update post and saying update the post that was just saved and make this its title so that it gets the like whatever name dash uh whatever date like that so that's how i'm using how i'm setting those field values this is a common question if you're getting started with wp graphql i see people asking this they think that if you use the wp graphql for advanced custom fields plugin that you get both the ability to query for acf field values as well as like update those values but you don't this plugin actually just allows you to query for acf data if you want to actually mutate those fields then you have to do some work like this you have to expose new input fields and then hook into an action you know like like this one and then actually say sanitize and save your additional data that came in so that's how i'm saving that additional data when a run is created right here i also have this user register mutation as well it does similar things so it gets passed in this the age group setter so remember that we saw that class already that's the one that is responsible for setting the user's age group after they register for this one i'm hooking into graphql register types and then i'm registering some input fields so these are used when the user is registering for the site i'm also calling before resolve field and i'm doing some validation just like we saw with the create run class right here so if we take a look at that it looks like this so make sure we're targeting the right mutation if we are i say if not is date of birth valid then throw an error you can see i'm doing a very similar check to the previous one i'm just saying if i can parse the date that was passed in using string to time then cool consider that you know a um a valid date that we can go ahead and use and ultimately save so if we pass that validation we then call we hook to this and call save additional data all right so you notice a pattern here right this is very similar to the the runs um mutation you know that we just looked at a moment ago so this one again i'm doing some data processing right here so i'm assuming this is the proper mutation that we're interested in targeting at that point i'm you know parsing the data uh birth that was passed in right there and then turning that into a string sanitizing it and saving three things saving the person's date of birth their city their state and this is where i actually use their date of birth data to set the user's age group so i'm using that age group setter that was passed in via dependency injection just like that and saved to this property i'm using that to automatically set this person's age group using their date of birth and then i set the uh the sex term to whatever they chose when they registered as well and that's how they get registered so so yeah wordpress knows how to save some of that data like when the user types in their first and last name and so on i don't need any custom fields for those of course because wordpress has those natively i just need kind of my custom ones here that i want to save in addition to those last one is the user object here let's take a look at what we're doing um yeah so for this one this is when we query for users i'm saying hey wordpress you already know about this type in the graphql schema called user but i want to be able to pull two additional fields for that person i want to be able to query for the sex that they selected when they registered so grab that object term and also for the age group that that person is in so pull that term as well and then return that value to me all right so with that i think we've wrapped up then um so you've you've heard the whole story of like where the app came from and and why it was built and launched in the first place uh i give you a tour of that app as though you know we were a user clicking around like logging our runs deleting our runs earning new badges and metals stuff like that and then seeing the leaderboard and where we rank and then we dove into the code so we take a took a look at the front end react and gatsby app and saw how that is all wired up and then also uh the wordpress backend to see how that functionality exists so thanks a lot if you stuck stuck with me um till the end thanks so much for taking the time to check out this project hopefully this is like um a cool case study to you and it kind of shows you at least one example of like what can be done with with the headless wordpress application um it inspires you to you know go on to build like really cool uh things yourself and um and launched some interesting projects so thanks a lot did you lock that go ahead and push that lock button subscribe
Info
Channel: Kellen Mace
Views: 3,345
Rating: undefined out of 5
Keywords: headless wordpress, headless cms, wordpress api, wordpress rest api, wpgraphql, wordpress, wpgraphql wordpress, graphql, gatsby.js, gatsbyjs, react.js, reactjs, wordpress react, react graphql, web, website, app, jwt, authentication, apollo, apollo client
Id: eA-FQLamZd4
Channel Id: undefined
Length: 117min 6sec (7026 seconds)
Published: Tue Dec 29 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.