
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
👉 Bunny (create your free account and get one extra free month): https://jsm.dev/jsm-bunny
How did you manage to remove the blur property and reach here?
Upgrading gives you access to quizzes so you can test your knowledge, track progress, and improve your skills.
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
In this lesson, we explore how to create a server action to fetch videos from a database for the homepage. We focus on implementing pagination and search functionalities, allowing users to filter and sort videos efficiently based on various parameters.
video.ts
file to fetch videos instead of creating or saving them.with error handling handler
.search query
, sort filter
, page number
, and page size
.build video with user query
to fetch videos along with user details.00:00:02 To fetch all the real videos for our homepage, we need to head over to the video.ts file and create a server action that'll now not create or save the
00:00:12 video details, but rather fetch all of the videos from the database.
00:00:16 So let's create it by saying export const get all videos and we'll wrap it with the with error handling handler.
00:00:26 and I'll say async and we can make this inner function accept some of the parameters that will help us paginate over all the different videos.
00:00:38 So we'll apply a search query which can be of a type string by default set to an empty string.
00:00:46 And since we'll have a few more of these, I'll actually put it each in a new line, something like this.
00:00:52 Next we'll have sort filter, optional of a type string, maybe a page number that we're currently on.
00:01:00 It's going to be of a type number by default set to one, as well as a page size.
00:01:06 Also, if I type number by default set to 8, so maybe we're displaying 8 videos per page.
00:01:12 So why do we have all of these right here?
00:01:14 Well, that's because once we visit the homepage, we can apply optional query parameters.
00:01:21 For example, we can say that search query is equal to test.
00:01:25 And if we do that, then we can automatically use these search params from the URL to sort, filter, or paginate our videos.
00:01:35 So we can maybe say that the page number is equal to 2, and based off of this variable, we can then paginate them.
00:01:41 So that's where we want to make this server action immediately super reusable in all of these different scenarios.
00:01:48 So within here, first things first, we want to get the currently logged in user.
00:01:52 So let's say const current user ID.
00:01:56 And we need to get this to be able to figure out which videos do we display for this user.
00:02:00 So this will be equal to await auth.api.getSession.
00:02:07 and we need to pass over the headers equal to await headers just like this and this will be resolved with a user so we can say const user but then below
00:02:19 it we can say const user id or we can call it the current user id will be equal to user?user.id.
00:02:28 Oh, this actually brings us back the session, not the user.
00:02:31 So I'll say session?user.id.
00:02:34 Now, we need to sort the base visibility, either public or owned by the current user.
00:02:40 So I'll say const visibility condition is equal to OR, and this OR is coming directly from DrizzleORM.
00:02:49 And then to it, we can say that the visibility condition has to be equal to, so EQ from DrizzleORM, either where the videos.visibility is set to public,
00:03:03 Or if the videos.userID is set to the current user ID, like this.
00:03:14 So that's when we can actually see the videos.
00:03:16 So we could actually rename this visibility condition to canSeeTheVideos.
00:03:23 or maybe videos to be shown to this current user that's currently viewing the homepage.
00:03:28 So one more time, they either have to be created by this user or they have to be set to public.
00:03:35 If they're private videos by another user, then this user should not be able to see them.
00:03:40 Next, we can also search by title.
00:03:43 So I'll say const where condition is equal to search query dot trim.
00:03:49 And if a search query exists, then we want to say, and from just ORM and attach the can see the videos.
00:03:58 And now that we know that which videos we're searching for, we can then check whether the title matches the search query.
00:04:05 So I'll say, does title match?
00:04:07 This is coming from a utils.
00:04:09 And to it, we need to pass the videos we're searching for, as well as the search query.
00:04:14 Perfect.
00:04:15 So we're matching those two conditions right here.
00:04:17 And if we don't have access to the search query, then we just want to match by the CanSee videos.
00:04:24 Just like this.
00:04:24 Finally, now that we have these two conditions that'll filter out the videos, we can get access to the total number of videos so we can paginate over them.
00:04:33 So I'll say const, destructure the total count and make it equal to await db.select the total count, which will be of a type SQL number,
00:04:47 and then count everything like this.
00:04:50 And we want to get it from videos where we match the where condition.
00:04:56 Perfect.
00:04:57 So now we're getting the total count and we can turn that total count into the number of total videos by saying total videos is equal to And we'll just
00:05:07 take a number constructor and wrap the total count with it, or set it to zero by default.
00:05:12 And then based off the total number of videos, we can create a total number of pages.
00:05:17 So I'll say total pages is equal to math.seal total videos over page size.
00:05:26 So if we have 16 videos and if the page size is 8, that means that total pages will be set to 2. And finally, we want to fetch the paginated sorted video results.
00:05:38 So I'll say const video records is equal to await build video with user query.
00:05:48 And this is a new helper function that I want to create so we can reuse it later on.
00:05:53 What this will do is not only return the videos to us, but also join the users that created them.
00:05:59 So I'll create this new callback function right at the top under helper functions by saying const build video with user query.
00:06:09 And we can just make it automatically return a DB dot select, where video will be set to videos, user.
00:06:19 From it, we want to select the ID equal to user ID, the name equal to user dot name, and the image equal to user dot image.
00:06:32 We want to take this from videos, and then we want to left join the user, where EQ equality, videos dot user ID, is equal to user.id.
00:06:46 And where is this user coming from?
00:06:48 Well, we can import it right from our schemas.
00:06:51 So just say user and get it from add forward slash drizzle schema.
00:06:56 Perfect.
00:06:57 Now, if we head down, we're now building this query right here.
00:07:02 And on it, we can call the .ware where the where condition that we pass in and we can also apply some sorting where I'll say that order by sort filter
00:07:14 that we have created above.
00:07:15 So if a sort filter exists, then we want to get order by clause and you can import it from utils and to it, we need to pass the sort filter.
00:07:28 Or if we don't have any sort filters, then we can just trigger an SQL search right here, where we'll sort it based on videos.createdAt in a descending order,
00:07:41 just like this.
00:07:42 We're also going to limit the amount of videos per page to the page size that we have set before.
00:07:48 And we also want to offset it, so .offset, by page number minus 1 times the page size.
00:07:57 So what is this?
00:07:58 Well, this is how we implement pagination.
00:08:01 If the page number is 2, we want to decrease it by 1 because we're already viewing that page and multiply it by the number of elements per page.
00:08:11 So if we're currently viewing the second page, that is 2 minus 1 is 1 times 8. So we want to skip the initial 8 and then move over to the second 8. And finally,
00:08:22 now that we have all of these video records and pagination details, we can return it all within an object.
00:08:29 By saying videos, we can rename it to video records, pagination.
00:08:35 And here we can pass the current page equal to page number.
00:08:39 We can pass the total pages, the total videos.
00:08:45 and the page size.
00:08:47 And this will now allow us to call the server action right from our homepage.
00:08:52 So head over into the homepage.
00:08:54 That's going to be root right here.
00:08:56 And instead of rendering the dummy cards, we can fetch the actual videos.
00:09:01 So right here at the top, I'll first get access to the search params of the page we're on.
00:09:06 So destructure the search params, and this will be of a type, search params.
00:09:14 Then we want to destructure the important things from search params, such as a query, a filter, and a page that we're currently on.
00:09:22 And that's going to be equal to await search params.
00:09:26 It's so simple to do it in Next.js.
00:09:28 Since we added await, we have to also add async right here at the start.
00:09:33 And then we can call the await getAllVideos server action, to which we need to pass a query, a filter, and a number constructor,
00:09:45 which will turn this page into a number.
00:09:47 Or by default, we can make it one.
00:09:50 If we call it with all the right parameters, we can now destructure the videos as well as the pagination details directly from that server action call,
00:10:00 which means that now we should be able to just map over those video cards, not by using the dummy cards, but this time using the real details.
00:10:11 So right here, I will remove this part for now.
00:10:15 And I'll say, if videos?length is greater than zero, in that case, we want to open up a new block where we'll render a section that'll have a class name
00:10:30 equal to video grid.
00:10:34 So we don't need this video grid right here.
00:10:37 Rather, this is the first section that'll be a video grid.
00:10:40 Next, if we don't have any videos, in that case, we can render an empty div that says empty.
00:10:47 Now let's indent this a bit better.
00:10:50 To be able to see this one video that we have on our dashboard, we can render videos, zero, dot video, dot title.
00:10:59 And check it out, we have a video called 3JS animations.
00:11:02 At least I do.
00:11:04 But now, what would happen if we didn't have any videos?
00:11:07 If the user has just joined for the first time and nobody else uploaded anything.
00:11:12 In web and mobile applications, these are called empty states.
00:11:16 So if I, for now, set this video length to greater than two maybe, and we don't have more than two videos, then we'll just see this empty state.
00:11:25 And instead of a simple text that says empty, let's actually create a nice reusable empty state component.
00:11:32 I'll do that by creating a new component within the components folder, and I'll call it empty state.tsx.
00:11:43 I'll run RAFCE and we'll make it reusable.
00:11:46 So I'll actually make it accept a couple of props, such as an icon that we want to display, a title.
00:11:54 So if we're in the homepage, we can display empty homepage.
00:11:57 But if we're in the profile page, we can say, you don't have any videos yet.
00:12:01 And then a description.
00:12:02 And these are going to be of a type empty state props.
00:12:06 After that, we can turn this into a section.
00:12:09 They'll have a class name equal to empty dash state.
00:12:15 We can render a div right within it.
00:12:17 That'll have an image with a source of icon.
00:12:20 So we'll render the icon we're passing from the homepage.
00:12:23 an alt tag of icon with a width of about 46 and a height of about 46 as well.
00:12:30 And then below it, we can render another div that'll have an h1 of title and it'll also have a p tag of description.
00:12:39 And you know what?
00:12:40 Since those two are related or adjacent, we can actually turn this into a little article.
00:12:45 So semantically, it makes a bit more sense.
00:12:47 So now that we have created this component, we can head back over to page and just render this reusable empty state component right here.
00:12:58 But of course, we have to pass some stuff to it.
00:13:01 So we have to pass the icon, title, and description.
00:13:05 So for the icon I'll just say assets icons video.svg for the title I will pass no videos found and for the description I'll say try adjusting your search
00:13:21 and close it right here.
00:13:23 If you do this and head back you'll see that now we have a bit of a nicer looking card that says no videos found.
00:13:30 Pretty cool, right?
00:13:30 But in this case, we actually have a video.
00:13:34 So we should map over all of those videos and display a great looking video card for each one of them, not just the title.
00:13:43 So we can do that by saying videos.map and we get access to each video that'll look like this.
00:13:53 And don't forget to automatically return something out of it.
00:13:56 So I'll close it and return right here.
00:13:59 And what will return is going to be the video card, self-closing component.
00:14:05 Now, the way that we structured the backend, the video is not actually here.
00:14:09 We have to destructure the video to be able to extract it out of it because next to it we also get the user.
00:14:16 So now we can pass a key to this video card of video.id And we'll also need to pass all of the other properties to it.
00:14:25 So we can try by just destructuring all of the video properties.
00:14:29 But we'll also need to pass some user properties, such as userImg is equal to user?image or an empty string.
00:14:39 And we'll also have to pass the username, which will be equal to user?name.
00:14:47 Or we can maybe do something like a guest.
00:14:50 Let's actually put this into multiple lines so we can better see what's happening.
00:14:54 There we go.
00:14:54 And now our video card is complaining just a bit.
00:14:57 It's saying that it is missing, let's see what, a thumbnail.
00:15:02 So where is the thumbnail coming from?
00:15:04 I think within the video we declare it as thumbnail URL.
00:15:08 So here we should say thumbnail.
00:15:11 And now it's getting everything it needs.
00:15:18 So if we go back right here, you'll notice that for the first time, we're trying to render an image that is not coming from our local system.
00:15:26 Rather, it has actually been hosted on the internet.
00:15:29 So you can copy this host name right here coming from Bunny, and you'll have to head over to next.config.ts and add images,
00:15:39 which will be an object where you can define remote patterns, which is an array.
00:15:44 and then you can define the first remote pattern.
00:15:48 Maybe you can set it as hostname, and then you can set it to what you copied it from right here.
00:15:53 For you, it's gonna be different.
00:15:55 So give it a hostname, also give it a protocol of HDDPS, a port of empty, and a path name.
00:16:05 of just forward slash anything then if you reload your application in the terminal back on localhost 3000 you'll be able to see the first video that we
00:16:15 have uploaded this thumbnail is real and is coming directly from bunny And of course, what matters most is that behind it somewhere,
00:16:24 we're actually hiding a real video that we uploaded.
00:16:27 Of course, for now, that's not gonna really cut it because to be able to see it, we have to display it on its dedicated video details page,
00:16:35 which we will do soon.
00:16:37 But this is so great to see because now we're not only uploading our videos with Bunny and securing the platform with ArcGit,
00:16:44 But we're also fetching that video and we can see it here.
00:16:47 Or at least it's card for now, but if the card is there, then it must also have a video URL, which we will display soon.
00:16:54 So let's do that next.