
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.
Complete source code available till this point of lesson is available at
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 explored how to implement a server action in Next.js that enables the fetching of tags based on various parameters such as filtering, pagination, and querying. The focus was on creating a reusable function that can be utilized across different parts of the application and handling the logic required to process these requests effectively.
00:00:02 The questions we're displaying on the homepage refer to some kind of tags.
00:00:06 You can see them here.
00:00:07 JavaScript, Node.js, TypeScript, and more.
00:00:10 But if I go to a specific tag right now, I get a 404. The page could not be found.
00:00:16 On top of that, We also have no way to see which are the most popular tags or filter by tags besides this right side section where we can see the number
00:00:26 of questions attached to each one of these tags.
00:00:29 But this data for now is fake.
00:00:31 We don't have 200 questions regarding JavaScript.
00:00:33 We have only one.
00:00:35 So what do you say that we implement a server action that will allow us to fetch all existing tags depending on different conditions like the search query,
00:00:45 filtering, and even pagination.
00:00:47 And of course, I'll also teach you how to make it reusable.
00:00:50 And then at the end of the day, it'll get used on the tags page as well as within some other sections of our app.
00:00:56 But keep in mind that we're going to use this server action to fetch data and not post it.
00:01:02 As you learned before, functions written in server actions inherently make post requests.
00:01:08 So you would think that this one would do the same.
00:01:10 Well, it will, but only if you use this function on the client side.
00:01:15 Only then will a POST request be made.
00:01:18 Otherwise, if you use the server action on the server side, it'll act as a normal function call as both the component where the function is called and
00:01:26 the function itself live on the server side.
00:01:29 This was just a recap on how server actions work.
00:01:32 But in simple words, always remind yourself.
00:01:35 Server action, when called through client or form submission, will make a post request to the server.
00:01:41 This is the only way that Next.js can actually post some data over from server to client.
00:01:48 but server actions when called inside server components will act as normal functions.
00:01:53 No post requests needed.
00:01:55 So, what can we take away from this?
00:01:57 Well, we can write this as a normal function or directly inside the component, but that will kill the goal of separation of concerns and make your code
00:02:06 much less readable.
00:02:07 So to keep things together while following the separation of concerns, we'll create a new file where we'll list all of its associated server actions.
00:02:16 Might sound a bit confusing right now, but trust me, it'll all start making sense very, very soon.
00:02:22 Let's create a new file within the lib folder, also within actions, and let's call it tag.actions.ts.
00:02:32 Within it, we can implement the getTags function.
00:02:36 That'll look something like this.
00:02:38 export const getTags is equal to an async function, and it'll get params as the parameter.
00:02:46 It'll be of a type, paginated search params.
00:02:49 We already have a type for that right here.
00:02:52 This is the type that handles the page, the page size, querying, filtering, sorting, and more.
00:02:57 So you can see, even though we use this on the homepage for the questions model, we can reuse it for the tags.
00:03:04 Super convenient.
00:03:05 Now here we can define what this function will return.
00:03:08 And in this case, it'll be a promise.
00:03:12 That'll return an action response, specifically that action response will contain the tags of a type tag array, as well as the isNext property,
00:03:23 meaning does the next page exist, which will be of a type boolean.
00:03:28 And then we can properly close it like this.
00:03:31 Now, let me actually explain what is happening over here.
00:03:35 We're creating a new get tags action, or in this case, you can think of it as a simple function.
00:03:41 It is an asynchronous function that accepts a single param object called params, which will contain information about the page,
00:03:48 page size, query, filtering, sorting options, and more.
00:03:52 And then to make our TypeScript happy and to make our code bug-free, we're specifying exactly what this function will return.
00:04:00 So while here you can define how the parameters look like, after the function declaration, you can define what that function will return.
00:04:09 This server action will return a promise of a type action response.
00:04:14 We already know how that looks like.
00:04:15 It contains a success information, data, error, and status.
00:04:19 And then specifically this one will contain the information about the tags as well as the isNext property for the pagination.
00:04:26 Now, so far, I wasn't really using ES6 arrow functions to declare server actions.
00:04:32 I use regular functions.
00:04:33 Either way is totally fine.
00:04:35 You can see this right here, export async function get tags, and then we don't have the actual arrow function.
00:04:42 This is also a valid way of doing it.
00:04:44 There's really no correct way.
00:04:46 Whatever you or your team prefers goes.
00:04:49 So you'll see me use both of these interchangeably.
00:04:51 What matters much more though is what is inside of this function.
00:04:55 And you might think, why are we going to start working on the inside if this part is complaining right here?
00:05:00 I mean, it's all red.
00:05:01 Well, it'll continue to be red because right now we're not returning anything.
00:05:06 But as soon as we start returning the proper things, the function will become green, or rather the error will go away.
00:05:12 So I think you already know a bit about how this works.
00:05:15 We're going to do things very similar to fetching the questions.
00:05:19 I mean, just check this out.
00:05:20 We first try to validate the params, meaning are we on a specific page?
00:05:25 Has the user entered a search query, maybe a filter?
00:05:29 Well, let me know.
00:05:30 If so, let's see if that is valid, and then let's destructure those params, and out of those params, let's apply those filtering options,
00:05:39 search for something, in this case, it was the questions, but later on, it'll be tags, filter them, and finally, fetch them and return them.
00:05:48 Believe it or not, we'll do absolutely the same thing for tags.
00:05:53 So instead of just copy pasting all of this, which you could totally do, and then just change everything from questions to tags,
00:06:01 I'm actually going to put it side by side so we can code it once again together so you truly understand how everything works.
00:06:07 Within this function, we first want to validate the params we're passing in.
00:06:12 So const validation result is equal to await action that is our handler action that makes all of our calls so much simpler.
00:06:22 And to it, we pass the params as well as the schema of what we're trying to validate.
00:06:28 In this case, it's going to be paginated search params schema.
00:06:34 What does this mean?
00:06:35 Well, that means that we're going to have a page, page size, a query, filter, and sort.
00:06:41 Same thing as we had for the questions on the homepage.
00:06:44 Once we get the validation result, let's check if it's correct or if it's corrupt.
00:06:49 We can do that by saying if validation result.
00:06:54 Same thing on the right side.
00:06:55 If you're paying close attention, if validation result is an instance of error, in that case, we can simply return handle error and then pass in the validation result.
00:07:08 But if everything is correct, we can destructure those params.
00:07:12 So let's destructure them here, const page by default 1, page size by default 10, query and filter.
00:07:19 So what you can notice here is that the params that we're grabbing from the URL are the same.
00:07:25 We're always looking at the page, page size, query and filter, but what will actually be fetched and displayed on the screen will be different based on
00:07:34 the page we're on.
00:07:35 So if we're in the homepage and we apply some kind of filter like React, that filter will filter out the questions collection and then display the appropriate questions.
00:07:45 But on the tags, if we apply some search or page or anything, it'll actually filter out the tags collection.
00:07:53 But still, a lot of the same logic applies, such as pagination and filtering features.
00:07:58 So let's copy this skip, limit, and filter query.
00:08:03 And paste it here.
00:08:04 And of course, let's import it.
00:08:05 Filter query from Mongoose.
00:08:07 And in this case, instead of type of question, it'll be a type of tag coming from a hat forward slash database.
00:08:15 After that, we can also check if there is a specific query by saying, if there's a query.
00:08:22 In that case, we can run the filter query dot dollar sign or.
00:08:28 And check if the name is equal to a regular expression that'll look something like this, $regex.
00:08:37 of query with the options of i for case insensitive.
00:08:42 Similar thing we've been doing here.
00:08:44 So in this case, we've been looking at the title or the content of the question, but in this case, we're looking at the name of the tag.
00:08:51 Next, we can also look into the sorting criteria by saying let sort criteria is equal to an empty object.
00:08:59 And then we can also do the filter.
00:09:00 See how we have done the filters here?
00:09:02 We can say switch.
00:09:03 We're going to look at the filter value.
00:09:06 And then refer to it as case.
00:09:08 Maybe let's do popular.
00:09:10 And we can simply then change the sort criteria to be equal to.
00:09:15 In this case, it'll not be the number of answers or when it was created.
00:09:19 In this case, it'll be the number of questions that is tied to each one of these tags.
00:09:26 A bit different, right?
00:09:27 Then we break it and we can add another case.
00:09:30 In this case, we can do case recent.
00:09:34 That'll be sort criteria is created at minus one.
00:09:38 We can also do the case of oldest.
00:09:41 Pretty self-explanatory, right?
00:09:43 That'll be the sort criteria of created at one.
00:09:46 We also need to break it.
00:09:48 And finally, we can sort it by name.
00:09:50 So we can do something like case is name or alphabetical is also fine.
00:09:55 where I'll change the sort criteria and make it equal to name 1 and break it.
00:10:00 And by default, we can apply the popular.
00:10:02 So show me the ones with most questions at the top.
00:10:05 Perfect.
00:10:06 So now we're applying the logic for filters as well.
00:10:09 Finally, we're ready to open up a try and catch block to actually fetch some of these tags.
00:10:16 I'll do that by saying try and catch.
00:10:19 In the catch, what do we do?
00:10:21 Well, we simply return the handle error to which we pass the error as error response.
00:10:27 And then in the try, we can try to fetch the total number of tags that we have, similar to how we fetch the total number of questions.
00:10:35 So that'll be const total tags is equal to await tag.countDocuments.
00:10:43 And we pass the filter query based off of which we want to retrieve the tags.
00:10:48 Next, we can get the tags themselves by saying const tags is equal to await tag.find.
00:10:58 And we want to find it based on the filter query.
00:11:01 We want to sort by sort criteria, skip by the number of skipped elements, and limit based on the limit of the number of elements per page.
00:11:09 Very similar to what we have done with the questions.
00:11:12 which is exactly why I wanted to have it side by side, to show you that when you create a system within your application,
00:11:18 be that a file and folder structure, or just a mental model of how you're going to make those calls, you see how simple it becomes.
00:11:26 We have done one thing with the questions, we are repeating it with the tags, and then every other further crowd operation,
00:11:32 for no matter which collection you're trying to fetch the data for, you can easily do it by following the structure that you already have in your code base.
00:11:40 And that's actually a funny story.
00:11:42 When you join a company, you'll most likely be given access to a huge code base, which will have tons of different abstractions,
00:11:51 different folders and files.
00:11:52 I mean, just look at this monster that we have created so far.
00:11:56 Thankfully, you now understand how all of these works because you have created them by following my lead.
00:12:01 So if you have already experienced that feeling of joining a company and then getting slammed in the face with a huge code base that you just immediately
00:12:09 need to find your way around, that's impossible.
00:12:12 Trust me, I've been there.
00:12:13 Or if you haven't reached that point yet, but you will in the future, well, there's one quick tip I can give you.
00:12:19 Everything follows a structure.
00:12:22 It's all about pattern recognition.
00:12:24 I mean, just take a look at our database.
00:12:27 It seems like there's a lot of files, right?
00:12:29 But once you dive into one of them, you'll start noticing similarities.
00:12:34 Look at the vote model right here.
00:12:37 We import some stuff from Mongoose, we export the interface that defines that specific model in a bit more detail, and then we export the interface for
00:12:48 how that document will look like in the database, and we create a model out of it, and we export it.
00:12:54 Then, if you compare it with the user, you'll notice it follows the same exact structure.
00:12:59 So a rookie mistake is thinking that you have to dive into all of these files and study them one by one to see exactly what they do.
00:13:07 But you don't have to do that.
00:13:08 You just have to understand the overall structure of your codebase and then everything will click and start making sense.
00:13:16 the same thing will go with our actions.
00:13:18 So far we have the question actions where we have four different server actions and now we're creating some for the tags.
00:13:25 So once we fetch the tags let's figure out if there are more tags on the next page by saying const is next is equal to if total tags is larger than the
00:13:36 number of skip plus the tags that length then that means that there is a next page Finally, we can return an object.
00:13:45 And now we're matching that object with what we're saying we will return right here.
00:13:49 An action response, which contains the success boolean and the data.
00:13:53 So let's do that.
00:13:55 Return.
00:13:56 Success is true because we haven't dove into the catch right here.
00:14:00 We're still in the try.
00:14:02 And we need to pass the data.
00:14:03 Now, how will our data look like?
00:14:05 How do we know that?
00:14:07 Well, check this out.
00:14:08 If you go into the action response, we're saying that the data is this kind of T.
00:14:13 But what is this T?
00:14:15 Do you know?
00:14:16 Well, if you go back, you can notice that here we're passing the T.
00:14:21 into the action response.
00:14:23 It's a generic type parameter.
00:14:25 So whatever we pass into it within these braces, the T will become.
00:14:30 In this case, the data is tags and is next.
00:14:34 So let's go ahead and pass that.
00:14:37 Data will contain the tags of json.parse and json.stringify tags, and we can pass over the is next.
00:14:48 And now you can see that it'll no longer complain right here, and it no longer complains right here either, because now we have created a new server action
00:14:59 that will allow us to get tags and also apply all the different parameters on them, like pagination, querying, filtering,
00:15:07 sorting, and more.
00:15:09 So with that in mind, let's put it to the test.
00:15:12 Let's head over into our app, root, tags, and then page.tsx, and let's call the getTags function.
00:15:20 We can do it right here within the function by saying const.
00:15:24 We destructure the success, the data, and the error, if there is one.
00:15:29 and then we await get tags And here we can pass different parameters, such as the page, which can be default to one, page size,
00:15:40 which we can default to 10, and a query, maybe of something like test.
00:15:45 Don't forget to import this get tags action and make sure to make this function asynchronous.
00:15:52 So we can actually have this await call.
00:15:54 Once you do that, we can extract the tags from the data by saying const tags is equal to data or an empty object if the data doesn't exist.
00:16:05 Finally, we can console.log those tags by saying console.log tags And then we can do json.stringify tags null 2. This will just make sure to display them
00:16:18 in a bit of a nicer way within our terminal.
00:16:21 Why terminal?
00:16:22 Well, because we're on the server side.
00:16:24 You can notice that nowhere here are we referring to client side functionalities.
00:16:28 So now if you head over to the tags page and reload.
00:16:33 Go back to the terminal and you can see that the tags array is completely empty, which is good.
00:16:38 We're trying to get some tags.
00:16:39 They don't exist.
00:16:41 No errors whatsoever.
00:16:43 But what if we remove this query of test?
00:16:45 Because of course, why would a tag with the name of test exist?
00:16:49 If I remove that, you'll see that now we're making a call to get all tags.
00:16:55 And now we have tags that belong to the question that we have created at the start.
00:17:00 I'm referring to this question right here, which has JavaScript, Node.js, and TypeScript tags.
00:17:07 So if you take a look at the console, you will see that we have the first tag of Node.js that belongs to one question.
00:17:14 We have another one, which is JavaScript, also belonging to one question.
00:17:18 And finally, TypeScript, which belongs to one question as well.
00:17:22 To test out the pagination, maybe we can try to retrieve only the first two elements.
00:17:27 So if I do that and reload, you'll notice that now we get two, which means that the pagination is working as well.
00:17:34 And let's try to test the search.
00:17:36 If I go ahead and type something like JavaScript, Now you can see that we only got one back, which is JavaScript.
00:17:42 So what I was trying to teach you in this lesson is that once you get the gist of it, things will start moving so much more quickly.
00:17:50 Even though this get tags is a beefy little server action, which has about 70 lines of code with complex validation handling,
00:17:58 param sorting, filtering, querying, and more, setting up all of the proper database calls and then calling it from the database.
00:18:06 It wasn't that tough, because we had something to refer to, and in that case, that was our getQuestions server action.
00:18:14 Next time, you'll have two functions to refer to, getQuestions and getTags.
00:18:19 And there's just gonna be more and more and more, and you'll reach a point where you won't have to refer to anything at all anymore,
00:18:25 because it'll all make complete sense.
00:18:28 With that in mind, now that we're properly and successfully fetching all of those tags, Let's go ahead and display them on the tags page and make it look
00:18:38 even better than it does on the current design.
00:18:40 Oh, but before we do that, let's not forget to commit.
00:18:43 I'll say something like implement get tags server action, commit sync, and we're good to go to the next lesson.