Fetching Posts Index0:00
Okay, let's work on communication between our frontend and backend. I think I want to cover all of the endpoints that we generated in the last video. So the index of Post, a single Post, and creating a Post. So let's jump right in. Let's go back to our frontend here. And this is the index page, which shows this page right here. So let's go ahead and fetch our data from our backend. So back to the docs, actually, let's grab the useFetch call, which we'll make use of for now. So let's grab this example.
for now. So let's grab this example. Let's go back to index, make a script setup here, script setup, and let's paste that in. Let's rename it to posts. And the endpoint is our Laravel app. So lcnux3bloglaravel.test. But if we try to use that domain, we're going to get CORS errors. So what I'm going to do is go to my Laravel project here, I'm going to say php artisan serve. And now this should be running on localhost port 8000.
serve. And now this should be running on localhost port 8000. So we shouldn't get CORS errors. So let's grab this. This is going to be our base URL, back to our frontend. Let's set that up. And I'm going to say localhost instead of 127.0.0.1. And the endpoint, if you remember, is /api/posts, okay. And let's see if we get anything back. So let me save this.
And let's see if we get anything back. So let me save this. Let's go back to our browser, back to our app. Let's open up DevTools here. And you can see that we are getting 21 Post back. So that's good. But each of these Posts requires the User as well. So let's make sure to grab that. So right now, if we go into a Post, you see that we only get the user_id here. So we have to eager load that relationship in Laravel.
Eager Loading User Data1:49
So right now, if we go into a Post, you see that we only get the user ID here. So we have to eager load that relationship in Laravel. So let's do that. Back to our Laravel app, back to our PostController. And back to the index method. Let's say Post with user. And this should work. Let's try that again. And you can see now we're getting a User object here, which is great. But we only want the ID and the name returned here.
And you can see now we're getting a User object here, which is great. But we only want the ID and the name returned here. So we want to minimize the amount of data being sent through the wire. So let's do that here. So we only want the ID and the name. Let's try that one more time. Let's refresh this again. And now we should get only those fields for the User. And we do. Cool.
Rendering Posts List2:39
And we do. Cool. So now let's go ahead and show them in the browser here. So back to our front end. Now we are looping over our post here. So let's say post in posts. Our key can be the ID. So post.id. And we want to pass in the post so that component has access to it. So let's say post equals post.
And we want to pass in the post so that component has access to it. So let's say post equals post. Let's save that. Let's go into our PostItem. Let's accept that prop down here. So let's make a script setup. We can accept props like this. So const props equals defineProps(). And let's say the posts are an object. Okay.
And let's say the posts are an object. Okay. Sorry, it should be post singular. Okay. And now we can make use of that in our template here. So for example, we can say props.post.title. Let's grab this and add the rest in. So this is the date which we'll format later on. For now, we'll just output the createdAt field. Okay.
For now, we'll just output the created_at field. Okay. This is the author. So there should be props.post.user.name. Okay. This should be the description. So let's say props.post.description. And these next links should link to the post id. So we can just say, let's grab both of them actually. And let's say props.post.id.
So we can just say, let's grab both of them actually. And let's say props.post.id. Okay. Does this work? Check that out in the browser. Let's refresh this. And I believe the description is named body. Okay. Let's change that. And that looks good.
Building Single Post Page4:32
Let's change that. And that looks good. So the link looks like it's working. This button looks like it's working as well. And all of the posts are now shown in the browser. Okay. Now let's work on the single post page. So let's go to one of these. And let's make sure to show the corresponding Post. Let's go back to our index.
And let's make sure to show the corresponding Post. Let's go back to our index. Let's grab the fetch call here. Let's go to the post page. So id. Let's go down here. Let's go ahead and use this. Should be a single post now. And the endpoint is going to be /api/post. And I'm going to make it a template string here.
And the endpoint is going to be /api/post. And I'm going to make it a template string here. So let's change that. And it's going to be /. And to get the route param, we can say route.params.id, just like in the template, but we have to grab that from useRoute. So const route = useRoute(). Okay. And I believe it's /api/posts. Okay.
And I believe it's /api/posts. Okay. And let's see if this correctly fetches our single post. So let's refresh this. Let's open up DevTools again. And you can see we are getting the corresponding post here. So again, we want the User information. So we have to eager load that. So let's go back to our backend for the show method. Let's go ahead and say $post->load('user').
So let's go back to our backend for the show method. Let's go ahead and say post load the User. And we want the ID and the name. So similar to what we did above, try that again, we should now have the User object. And you can see here and we have the name and the ID. So again, we can just fill out everything. So it shows in the browser here. So back to our front end, let's fill out everything here, there should be a post.title, post.title. And we can actually replace the title up here with the same thing. So let's grab that, let's put that in here.
And we can actually replace the title up here with the same thing. So let's grab that, let's put that in here. And let's try that out first. And it does work. You can see the title in the title bar here. Cool. Let's fill out the rest here. Again, this is the date, which we'll format later on, created_at, this is the author. So postUserName. This is the body, postBody, and I think that should be it.
Creating Global API Fetch6:56
So post user name. This is the body, post body, and I think that should be it. Cool. Let's do some cleanup with our fetch call here. You notice that we're making use of the base URL twice. So localhost:8000 in our case, so we're using it every time we have to fetch some data. So right now on the single post page and on the index page. So let's clean this up step by step. First I don't want to have to repeat the base URL. And there is an option here in useFetch or the underlying OhMyFetch library that lets
First I don't want to have to repeat the base URL. And there is an option here in useFetch or the underlying OhMyFetch library that lets you specify the base URL in an object here. So we can say baseURL, and we can grab this and put it here as a string. So if we get rid of this, this should still work. So this is for the index page. Let's double check that it still works. So back here, let's refresh, and it still works. So we can do the same thing in our single post page, but that doesn't really help us since we're still repeating the same baseURL every time we have to make a fetch call.
So we can do the same thing in our single post page, but that doesn't really help us since we're still repeating the same base URL every time we have to make a fetch call. So we want to have one global place where this is set, and then we can just make use of that instance. Now there are several ways you can do this, but I'm going to make use of plugins in Nuxt. So if you take a look at the docs here, you'll see that plugins are auto-registered just like everything else. And this is how you would make use of them. So we would export default a defineNuxtPlugin method, then we can put the fetch instance in here with the base URL and just make use of this plugin every time we need to make
syntax you can make use of within the docs here. So we can say nuxtapp.provide. So we'll do it this way. So we'll put this in here. So right now this is just a method that returns hello name. So to make use of this in our application, we can call it like this. So within $hello, or whatever you named it here. So let's go ahead and try this somewhere else in our application. So in this case, let's just put it in the index for now. Let's paste that in.
So in this case, let's just put it in the index for now. Let's paste that in. So we're just console.logging that method. And let's see if this works. And it looks like we have an error in the browser, we might have to restart our server here. Okay, so that's working now. But we're now getting a new error nuxtApp is not defined, you have to make sure to import that as well. So right here, or not import it, but grab it from the composable.
that as well. So right here, or not import it, but grab it from the composable. So let's do that. Let's just put it right above here. And let's go ahead and save this. Let's try again. And there it is. So what we want to do is provide an instance of the useFetch composable. Or in this case, I believe useFetch doesn't work in plugins. So we have to use the underlying OhMyFetch library.
Or in this case, I believe useFetch doesn't work in plugins. So we have to use the underlying OhMyFetch library. So let me show you what I mean. Let's go back to APIFetch. Let's rename this method to APIFetch. This is where we'll create our fetch instance with the provided base URL. So let's go ahead and do that. Actually let's take a look at the OhMyFetch docs to see how we can do that. So I have the docs open here. Let's look for .create.
So I have the docs open here. Let's look for .create. So you can see it says this is useful if you need to use common options across several fetch calls, which is what we need here. And we're just setting the base URL here. So let's go ahead and do that. Let's paste that in. And let's hard code the base URL for now. So localhost:8000. Okay, let's save this.
So localhost:8000. Okay, let's save this. Let's go back to our index. And now instead of using this fetch call, we can make use of our plugin. So that should have the definition for the base URL. So we don't need to define it here. So let's try that out. So let's comment this out. Let's move this up. And let's say const posts equals await, we can say $APIFetch, or NuxtApp.APIFetch.
Let's move this up. And let's say const posts equals await, we can say $APIFetch, or NuxtApp.APIFetch. Or if you want, you can just call it directly here. So use NuxtApp.APIFetch. So we don't need to grab it from here. Let's comment this out as well. And the endpoint is /api/posts. The base URL should be set. So hopefully this should work. Let's get rid of this too.
So hopefully this should work. Let's get rid of this too. Save this. And hopefully it's now making use of our plugin. So back here, let's refresh. And it still works. So now we can do the same thing for our other fetch call. So let's grab this. Let's go to our single post page. Let's comment this out.
Let's go to our single post page. Let's comment this out. Let's paste this in. Say single post here. And let's just grab the endpoint here. So /api/posts. And the route param. And it should be a template string. Okay, let's try that as well. Hopefully it works.
Okay, let's try that as well. Hopefully it works. And it looks like it does, cool. So one more cleanup here. We don't wanna hard code this into our code because we'd have to change it every time we go into production. So we can store it in an environment variable. So let's grab this. Let's make a new .env file.
So let's grab this. Let's make a new .env file. Let's call it BASE_URL. Let's paste that in. Save that. And let's say process.env.BASE_URL. Save that. We might have to restart our server. So let's do that. And let's see if this works.
is to make use of this runtime config here. So within your nuxt.config, you can define a public runtime config. So exactly like this here. And then in here, we can make use of that config instead. So let's do that. So nuxt.config. Let's add that right here. This is the correct environment variable.
Let's add that right here. This is the correct environment variable. Let's save that. And from within our plugin, we can grab the config here. So const config equals useRuntimeConfig. So back to API fetch. Let's use that in here. And now instead of grabbing it from the environment variable,
And now instead of grabbing it from the environment variable, we can grab it from the config. Let's save that. Let's restart our server. And hopefully we no longer have that undefined error in the browser. So let's try that again. Still works in the browser and we no longer have that error.
Creating Posts and Validation14:22
Still works in the browser and we no longer have that error. Let's double check that the single blog page still works. And it does, cool. Let's also work on creating a Post. I already added the markup for the form here, which you can find in the create.blade.php. Again, just a bunch of Tailwind classes. I have a v-model on the title and the body. And I have a createPost handler.
I have a v-model on the title and the body. And I have a createPost handler for when we submit the form. So we have state here and that function for creating a post. Let's go ahead and wire this up to our backend. So let's grab an endpoint call from one of our other pages here. Let's grab this. And let's put it right here.
Let's grab this. And let's put it right here. Okay. You have to make sure this function is async. The endpoint for creating a Post is /api/post. Okay. And to specify it's a POST request, we can add it to the options here. So we can say method post and we can also add the body in here.
So we can say method post and we can also add the body in here. So the title is title.value and the body is body.value. And after that's done, we want to reset the form. So we can just title.value equals empty string and same with body. Let's also redirect back to the main page. So we can say router.push to the homepage. Don't know why that changed.
So we can say router.push to the homepage. Don't know why that changed. We do have to grab the router though. So let's say const router equals useRouter. Okay. Let's save that. And let's see if this works. Let's refresh just in case. Let's say myNewTitle. This is the body. Let's open up DevTools.
This is the body. Let's open up DevTools because I don't think this is going to work. Let's open up the network tab and let's try to submit the form. Okay. And we do get an error. The error is CSRF token mismatch. And this is because we're using Breeze on the backend and it does expect a CSRF cookie. So we'll take a look at this later on.
and it does expect a CSRF cookie. So we'll take a look at this later on when we do auth. But for now, I'm just going to disable that. So in kernel or app/Http/Kernel.php, there should be a middleware here. It says ensureFrontendRequestsAreStateful and this is added by Breeze. So let's get rid of this for now. So let's comment that out.
We get the alert and we should be redirected back to the index page. And we do. Cool. So if I hide DevTools here, you'll see that new post up here. Let's go back to the create page. You can also add a loading indicator if you like. So I already have the state for that. Let's go back to our frontend. We have this is loading state,
Let's go back to our frontend. We have this isLoading state, which is false by default. So we can set it true here. So isLoading.value equals true before we make the call. And then after it's done, we can set it back to false. And you can make use of this in the template as well. I'll just put it somewhere here.
And you can make use of this in the template as well. I'll just put it somewhere here. You can make it a loading spinner or whatever you like, but I'll just add some text here. Let's say loading .... And we'll put a condition on this v-if equals isLoading. Or v-show will work as well. Let's stick with show.
Let's stick with show. Okay. Let's make it slower on the backend so we can see it. So back to our PostController for storing. Let's add a sleep here of three seconds back to our browser. And let's see if we get the loading indicator. So let me just fill this out. And let's see if we get the loading text and we do, cool. Okay. Now let's take a look at errors.
And let's see if we get the loading text and we do, cool. Okay. Now let's take a look at errors. So back to create. You can see I already have this placeholder for errors here. So let's take a look at that and then we'll call it a day. Let's get rid of the sleep here. Back to our front end. And for our async await call, we have to wrap it in a try catch. So we'll put it right here.
we have to wrap it in a try catch. So we'll put it right here. So we'll try this. And down here is where we can catch the error. So catch, let's just say error, and we can respond accordingly. Let's set the isLoading to false as well. So let's add that. Let's just console.log error.data, which should be the error response coming from Laravel.
Let's just console.log error.data, which should be the error response coming from Laravel. So let's try that out first and then we'll add it to our template. Okay. Let's just refresh. Let's open up our console. Let's just submit the form. That should result in validation errors. Let's go to our console.
That should result in validation errors. Let's go to our console. And you can see the errors here. So we do have this errors array or errors object, and we can see the errors for each field here. So back to our code. I already have a piece of state for errors. It's an empty array to start, but we can set that here. So errors.value equals,
but we can set that here. So errors.value equals, let's grab the values for the objects. So Object.values. We can grab that error.data.errors. And let's just flatten that out. And then in our template, we can loop over those. So up here, we'll make it an unordered list instead. So let's get rid of this. Let's say <ul>,
So let's get rid of this. Let's say ul, and only if there are errors.length greater than zero. And I'm just gonna paste in some class names here because this video is getting quite long. Just some styling. And then for the list items, we can loop over those errors. So v-for equals, let's say error and the index in errors.
So $v4 equals, let's say $error and the $index in $errors. And the $key is the $index. And the $error itself will be the $message. So we can just output that. Let's just say $error. And hopefully I did that correctly. Let's save that. And let's see if the $errors appear in the browser. So let's refresh.
And let's see if the errors appear in the browser. So let's refresh. errors.length. I spelled errors wrong. Try again. Okay. Let's just submit the form. And we do get the error messages here. So I believe there's a minimum of three characters for the validation.
So I believe there's a minimum of three characters for the validation. So if we try one character, that message should change. And it does, cool. Okay, two more quick things I wanna do. One is the formatting on the index page. I want this to only be a certain amount of lines long. For this, we can make use of the line-clamp plugin from Tailwind.
For this, we can make use of the line clamp plugin from Tailwind. So let's go ahead and install this. Stop this. Install this. Okay. Let's add it to our tailwind.config.js. So tailwind.config.js. plugins. Okay.
Plugins. Okay. And let's go back to our index here. Actually, it's in our Post item for our body here. We can say lineClamp. And in this case, I just want three lines. And that should limit it to three lines. So let's save that. Let's run our php artisan serve again to make sure it works. And if you go back to our app,
Let's run our server again to make sure it works. And if you go back to our app, you can see that the post preview is maximum three lines long. Okay. And last, let's take care of the date here. So I'm gonna make use of date-fns, which works in the browser and also in Node, which is great. Let's go ahead and install this. Stop this.
Let's go ahead and install this. Stop this. Install this. Okay. Let's run our server again. And we're gonna make use of the format method. So there's an example up here. Let's format it or let's import it. And then we can use format. So back here, let's go to our post item.
And then we can use format. So back here, let's go to our post item. Actually, we're already there. Let's go down here to import it. Don't need this. Okay. And now we can make use of it wherever we need to format the date. So right here, you can say format.
So right here, you can say format. Needs to be a date instance, so we can say new Date. Okay. And the format I want is the month, the day and the year. Okay. Does this work? Let's save that and let's see if it formats the date. And it does.
Let's save that and let's see if it formats the date. And it does. Cool. Let's make sure to do the same thing on our single post page. So I'll grab this. Let's go back to our ID. So right here, should be post created_at. Make sure to import that as well. Let's grab the import from here.
So we do have our backend and frontend. So let's just say, git add, git commit, communication with frontend. And for our frontend, git add, git commit, communication with backend.
