Introducing Server-Fetched Partials0:55
It's not the entire page. So consider for a second how you might write this if you had to implement this feature. You might use something like Vue.js to listen for a click, send an AJAX request to your server, and fetch some JSON. And then with the JSON, store it in the data maybe, and the template will automatically react. So that's what I would probably have done before I encountered server-fetched partials. So let me show you. GitHub actually uses server-fetched partials all over the place in their app. It's where I first encountered it.
GitHub actually uses server-fetched partials all over the place in their app. It's where I first encountered it. So if we go to our network tab and then filter by XHR, that's going to give us all the AJAX requests. And now watch what happens when I click this refresh button. So check it out. There was an AJAX request sent, but instead of returning JSON, GitHub just returns the back end of GitHub just returns HTML, pre-rendered HTML. So for us, this would be like having an endpoint that just returns a Blade view, just a partial, like a Blade include, renders it, spits out the HTML, and then maybe there's some JavaScript.
So for us, this would be like having an endpoint that just returns a Blade view, just a partial, like a Blade include, renders it, spits out the HTML, and then maybe there's some JavaScript on the page that subs it into the section of the page. Something pretty simple like .innerHTML that just shoves it into the page and doesn't do anything fancy. So this is server-fetched partials. So let's actually implement this feature inside of a Laravel app and see what it looks like. Then we'll dig a little deeper and explore some of the tangible benefits of an approach like this over using something like Vue. So here's my little sample, explore the GitHub sponsors community.
Reviewing Laravel Page Setup2:13
like this over using something like Vue. So here's my little sample, explore the GitHub sponsors community. We have some sponsors that I have in a Laravel database, and then I hit refresh and nothing happens because we didn't implement it yet. So let's take a look at the code, get caught up to speed. Then we'll actually implement the server-fetched partial. So here's our basic route that loads this whole thing. First order of business, get the users out of the database. We're going to get them in random order, limit it to five, and we're going to pass this into a Blade view called sponsorships.
We're going to get them in random order, limit it to five, and we're going to pass this into a Blade view called sponsorships. So let's take a look at sponsorships. So here it is, fairly straightforward. We have a bunch of boilerplate HTML that I put together with Twitter Bootstrap. There's our refresh button that does absolutely nothing right now. And then here's kind of the meat and potatoes. Here's where we actually interact with the data. We're foreaching through all the users that we passed in, and we're rendering each one in its own row with some information, the username, the avatar, the bio, things like
Extracting a Blade Partial3:05
We're four-eaching through all the users that we passed in, and we're rendering each one in its own row with some information, the username, the avatar, the bio, things like that. Okay, so our first order of business is to take this bit of HTML or Blade, extract it into a partial, and then put it on its own dedicated endpoint so that we can fetch it with JavaScript. So we're going to cut this out, and we're going to create a new view, and I'm going to create a blade include or a partial. So when I create partials, I prefix the name with an underscore that kind of tells me that, hey, this isn't a full HTML page, that you can't render this as a full page.
So when I create partials, I prefix the name with an underscore that kind of tells me that, hey, this isn't a full HTML page, that you can't render this as a full page. This is just a bit of it, just a subsection. So I'll say _developers.blade.php, okay? And then we'll paste that in there. All right, pretty straightforward. And inside here, we'll put a comment in for aesthetics. Great. Now we can put this on its own separate endpoint. So inside our routes file, we'll duplicate this, and we'll create a new route called
Now we can put this on its own separate endpoint. So inside our routes file, we'll duplicate this, and we'll create a new route called partials developers, okay? We get the users out of the database, we pass them into the Blade view, but instead of sponsorships, we're going to actually just render that include or that partial, so say developers, all right? And now we've removed the part that actually used users inside of the Blade file, so we can get rid of this altogether, okay? So now we've separated out this page into two separate endpoints, one that loads sort of the shell of the page, and then one that loads the partial.
So now we've separated out this page into two separate endpoints, one that loads sort of the shell of the page, and then one that loads the partial. So let's take a look at this in the browser. All right, so now if we refresh, we don't get any developers because we yanked that portion out. And if we duplicate this tab and go to the partial endpoint, so partials developers, oops, not developers, great. And check it out. We have just that portion rendered as the entire page. So we have two separate endpoints for both of the pieces of the puzzle.
Fetching Partial with JavaScript4:59
We have just that portion rendered as the entire page. So we have two separate endpoints for both of the pieces of the puzzle. So let's wire this up so that when we load this page, we fetch from that endpoint and sub that HTML into this portion, okay? So we're going to do this with vanilla JavaScript. But really, what I'm trying to demonstrate to you is the technique or the concept. And you can implement this however you want to. You could even use Vue.js to accomplish the same thing. So for starters, I want to create a function that's in charge of fetching that partial. So let's create a function called fetchDevelopers.
So for starters, I want to create a function that's in charge of fetching that partial. So let's create a function called fetchDevelopers. And we'll use just the native fetch API to do this. We'll say partials/developers. We get the response back as HTML. And now we want to get this element from the DOM, this UL. So we'll use document.querySelector. And for now, let's just say UL. And then .innerHTML. If you haven't seen .innerHTML, it's pretty straightforward.
And then .innerHTML. If you haven't seen .innerHTML, it's pretty straightforward. It's a property of an element. So if you call .innerHTML on any DOM element, it'll give you back the HTML inside of it. But it's also a setter. So we can say equals HTML. And it'll convert everything inside of that element to whatever we pass in. In this case, the partial that we got from the server. But notice my query selector is UL, which is pretty broad and generic. And presumably, there'll be other ULs on the page.
But notice my query selector is UL, which is pretty broad and generic. And presumably, there'll be other ULs on the page. So you might think to do something like, I don't know, you make an ID called developers or something like that. And this is okay. But there's a problem here for me. When you're scanning this HTML, it's not apparent that JavaScript depends on its existence. So there's a little trick that I picked up from the jQuery world in my jQuery days to prefix these kind of IDs with JS. So this expresses that JavaScript cares about this element.
prefix these kind of IDs with JS. So this expresses that JavaScript cares about this element. So I can say JavaScript, let's do JS-. And let's say developers partial target. Extremely explicit. I like to be as explicit as I possibly can, because there's a little bit of a magic link between this JavaScript and this element. So we'll take that ID, and we'll shove it into here. Okay. And now we can call this function on load.
Okay. And now we can call this function on load. So we'll say fetchDevelopers, right here. So we define the function, and then we call it. So when the page loads, it's going to define this function fetchDevelopers, then it's going to run it, fetching the partial from the server, taking the HTML, and swapping it into the page on this ul element. So let's actually run the page and see if it worked. Great. It totally worked.
Great. It totally worked. And if we look at our network tab, refresh, and there we go. We're fetching from an Ajax endpoint, and we're getting just that partial and swapping it into the page. Exactly how GitHub does. So now the easy part, we can wire up this refresh button. So really, we just want to listen for a click on refresh and run fetchDevelopers. So I'm actually just going to register this, this click handler right inside of the HTML. This is kind of an old school way of doing things, but it's still totally valid.
Testing Partials in Laravel8:56
it from the perspective of testability, of testing your applications. So I want to show you kind of a tangible proof of why I generally reach for something like this over Vue.js whenever I can, because I get so much testing benefit. So let's wrap up what we've done inside of a test and then refactor it to Vue.js and see what the benefits are. Okay, so let's create our test. Can make test. We'll call this sponsorshipsTest. Okay, and we'll get rid of this boilerplate. And we'll create our first test.
Okay, and we'll get rid of this boilerplate. And we'll create our first test. We'll say canViewSponsorshipsPage. So this is going to be that shell page that just has the HTML and the JavaScript that loads the partial. So this is a pretty straightforward test. We're going to just hit the endpoint. Then we'll assert successful. All right, we run the test and we get green. Awesome.
All right, we run the test and we get green. Awesome. So now let's wrap up that partial inside of a test. So let's say canFetchDevelopersPartial. And now instead of hitting the root endpoint, we'll hit that partials endpoint. So partials/developers. And for now, we'll assert successful and see if that passes. Okay, it passes. Great. But I want to test something a little bit more meaningful because this partial actually
Great. But I want to test something a little bit more meaningful because this partial actually gets users out of the database and loops through them. Let's actually test that. So we'll factory up five User models from the database. Okay, and now we can actually make assertions on the data that gets passed into the view. So we'll say assertViewHas users. And we can just make that assertion and that's fine. But I want to assert something about those users. For me, right now, I'm fine with just making sure that there's five of them.
But I want to assert something about those users. For me, right now, I'm fine with just making sure that there's five of them. Whatever they are, I just want to make sure there's five. And that'll do it for me. In a real world scenario, I might do some further tests. I might test that the proper names come through, that the IDs, things like that. But for now, we'll add this callback. We'll pull in the users that get passed into the view. And now if we return true, the assertion passes. If we return false, it fails.
And now if we return true, the assertion passes. If we return false, it fails. So let's say return count($users) == 5. Great. So we created five Users and we want to make sure that all five make it to the view. We run the test and we're still passing. So we run both tests and we pass. Now let's go to the routes/web.php file. This is the back end of our app. And if we change anything in the back end, we're now totally covered by our tests.
This is the back end of our app. And if we change anything in the back end, we're now totally covered by our tests. So anything we mess with, if we break anything, we limit four and we get a failure. So this gives me confidence in my app. I can run my test suite and I know that this portion of the app is covered under test. So that's the back end which is tested. But one benefit of using server fetched partials is all of our templating is done in Laravel. So if I go to that developers partial and start messing with it, check this out. We get a failure, which is really helpful for me. Because if this lived in JavaScript, if this was a view template, we wouldn't get a failure.
We get a failure, which is really helpful for me. Because if this lived in JavaScript, if this was a view template, we wouldn't get a failure because phpunit does not test JavaScript. So here is probably the most popular error that I've encountered in all of my days writing Laravel apps. Let's say that you had a User and then it had a relationship to Account. So a User has an Account and there's a foreign key called account_id on the users table, something like that. And then username. But for some reason, that Account doesn't exist for this User.
And then username. But for some reason, that account doesn't exist for this user. Maybe you didn't eager load the relationship properly, or maybe for some reason that user didn't get it created and you didn't know that. Now if we run the tests, we get a failure. So look at that failure, trying to get property username of non object. If you've been writing Laravel apps for any amount of time, you have encountered this. And this probably gives you shivers because this happens so often. This is the thing that most commonly bites me in production. But the great thing is about keeping all of our templates inside Laravel is that we're
Refactoring Feature to Vue12:54
This is the thing that most commonly bites me in production. But the great thing is about keeping all of our templates inside Laravel is that we're totally covered for things like this for free. We just wrote our test, we asserted against the data, but it also covers all of our templating. So this is probably my favorite advantage of using server fetched partials. So let's refactor this entire feature to use Vue.js and see if we can break the same things and get the same amount of test coverage and what the pros and cons are. So to demonstrate the differences, I created an entire Vue component that handles all of this functionality for us to kind of demonstrate how I would do this in Vue. So it's called developers.vue.
this functionality for us to kind of demonstrate how I would do this in Vue. So it's called developers.vue. It's a Vue component. It has this big template that has everything from before. We have the ul where we're looping through the users and showing all their data. And then the functionality is fairly simple. We're hitting an API endpoint to get the JSON of the users, putting it in as data, and then exposing that as a method called fetchDevelopers. So when the Vue component first runs on the page, it fetches the developers. And then every time a user clicks that refresh button, here's the button, we're doing at
So when the Vue component first runs on the page, it fetches the developers. And then every time a user clicks that refresh button, here's the button, we're doing @click fetch developers. And it's rerunning that method, regetting the JSON, putting it into the data, and now everything automatically refreshes. If you're not familiar with Vue, or if I'm throwing too much at you, the point of what I'm doing right now is to show you the different approaches and their testing trade-offs. So it's okay if you don't necessarily understand everything that's going on inside this Vue component. But I just want to show you the broad overview of the differences here.
component. But I just want to show you the broad overview of the differences here. So we can take everything we had before and replace it with this Vue component. Okay, now if we load the page, we're kind of back to square one. And the reason we're back to square one is we didn't actually implement that JSON API endpoint. So it's actually trying to fetch from an endpoint that doesn't exist. So it's called api/developers. So let's actually implement that. So instead of partial/developers, we'll say api.
So let's actually implement that. So instead of partial slash developers, we'll say API. And now instead of returning the Vue, we can just return the data. Okay, refresh the page, and everything's working. And now check out that AJAX request. Instead of HTML, now it's JSON. And if we hit the refresh button, it's still working. So this is how you would write it in Vue. Now let's massage our test to account for these changes. So sponsorshipsTest, the first test is going to be the same, because we're just asserting
Now let's massage our test to account for these changes. So sponsorships test, the first test is going to be the same, because we're just asserting on that shell page. The second test, instead of fetching a partial, we're going to hit the API endpoint. And instead of assert Vue has there is no Vue, we'll make an assertion on the JSON. So we'll say assertJsonCount. And we can just say five users. So this does the exact same thing. We're saying seed up five users, hit the API endpoint, and make sure you see five things and the whole thing works.
We're saying seed up five users, hit the API endpoint, and make sure you see five things and the whole thing works. Great. And we can go in here and break things. And our tests are going to fail, which is great. So we're still covered for this back end. But our front end is not covered by these tests. So if we start breaking things in the front end, and we run the tests, they're still passing. If we simulate that issue, that user.account.username, rerun the test, we're still passing, which is a false positive.
If we simulate that issue, that user.account.username, rerun the test, we're still passing, which is a false positive. If we go into our application, we refresh, everything's broken. And take a look at this error. And there we go. We did not read property username of undefined. This is the JavaScript version of that PHP error we encountered before. Because with something like Eloquent, or web apps in general, if some piece of data doesn't exist, and you're accessing data on that data, you get errors. And this sort of thing happens all the time.
