
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 explore the implementation of the "edit question" action within a server-side context, focusing on how to handle interactions with different models such as questions, tags, and tag questions. We discuss the necessary logical steps involved in editing a question, including user validation and updating tag relationships.
00:00:02 Before we implement the edit question action, we'll have to think about the logical steps of how this is supposed to work,
00:00:09 since we'll be interacting with three different models.
00:00:12 The question itself, the tag, and tag questions.
00:00:16 First, we'll take in the title, content, and the tags in the same way we took them in the create question action.
00:00:23 But this time, we'll also take in a question ID as another input.
00:00:28 That way, we'll be able to fetch that question from the database based on the question ID and ensure that the current user is indeed the author of that question.
00:00:37 Next, if any of these three fields have changed, like the title, content, or tags, then we can update the question with the new data and save it.
00:00:46 And we'll handle the tags in the same way we handled it before.
00:00:49 If a tag already exists in the database, then we can simply increase its tag count by one.
00:00:56 Else, if it doesn't exist, we will first create a new tag with that name.
00:01:00 After handling the tags, we will associate them with the question.
00:01:04 Finally, we will update that question, save all changes, and commit the transaction to ensure consistency.
00:01:10 So within question.action.ts, we'll create a second server action.
00:01:15 I'll create it just below by saying export async function, edit question.
00:01:24 We can open up a function block like before, and we'll also take in some similar imports, such as these params right here,
00:01:31 and a promise that returns a question.
00:01:34 As well as the validation result, because we have to validate it, we have to check for errors, we have to extract the params,
00:01:42 get access to the user ID to know whether this user is the original author of the post, start a session, start the transaction of that session,
00:01:52 open up a try block, And I think this is enough.
00:01:55 We can do the rest on our own, but for now, we can just copy this boilerplate part that almost every function has.
00:02:01 So let's copy it.
00:02:03 And let's put it to our edit question.
00:02:06 So that's going to be edit question.
00:02:08 And we need to properly close it right here at the bottom.
00:02:12 Great.
00:02:13 Now, instead of having create question params, here we'll have edit question params, which is going to look like this.
00:02:19 Interface, edit question params.
00:02:24 We'll actually extend by saying extends create question params, but this time we'll also append a question ID of a type string.
00:02:35 Super smart, right?
00:02:36 You don't have to re-declare everything.
00:02:38 You're just saying, hey, give me everything a question has and then also a question ID so I can find that question in the database so I can edit the rest
00:02:46 of the fields.
00:02:47 So now here we can say edit.
00:02:49 question params, and we'll also have to have a special, not ask question schema, but rather edit question schema.
00:02:57 So let's head over to our validations.ts.
00:03:00 Here we have an ask question schema that we've had before, but now we have to do export const edit question schema, which is going to be equal to ask question schema.extend.
00:03:16 Okay.
00:03:16 So we're doing exactly the same thing as when creating TypeScript interfaces.
00:03:20 Now we're doing that for Zod frontend validation, where we can say question ID has to be of a type z.string.min1 question ID is required.
00:03:32 Perfect.
00:03:33 So now we can head back over and we can pass in the edit question schema coming from validations.
00:03:41 Now that we have that from params, we can also extract that question ID.
00:03:46 We get the user ID, we start the session, and then we can open up a try and catch block.
00:03:51 The catch and finally will be pretty similar.
00:03:54 In the catch, we will simply await session data board session and return handle error as error response.
00:04:01 And in the finally, we will simply await session dot end session so we can properly commit all the changes we did.
00:04:10 And in the try, while it'll be similar, there will be some differences.
00:04:15 First things first, we have to get access to the question by saying const question is equal to await question dot find by ID to which we can pass the question ID.
00:04:29 And this one, we actually don't have to attach to a session.
00:04:33 So then we have to extract it like this.
00:04:35 We don't have to give a session because finding things or getting things is not a destructive or a mutating action.
00:04:42 Whatever we're doing, it won't make any changes to the database whatsoever.
00:04:46 So there's no need to add the session to be able to revert it for the get actions.
00:04:51 But what we will do is say .populate and we will populate the tags.
00:04:57 The .populate method specifies paths which should be populated with other documents.
00:05:02 So as you know, our question contains the IDs of the tags within it, but not the actual tag data.
00:05:09 This will populate it with the data.
00:05:11 Finally, if there is no question, we can throw a new error saying question not found.
00:05:18 But if the question.author.to string is not equal to the user ID, we'll throw a new error saying unauthorized, because that means that the user that is
00:05:32 trying to edit it is not the actual creator of the post.
00:05:35 And then finally, we want to check for which fields have been updated by saying if question.title is not equal to the title,
00:05:44 or if question.content is not equal to the content, in that case, we can set the question.title to be equal to the new title,
00:05:54 and we can set the question.content to be equal to the new content, and we can finally say, wait, question.save, and we can pass the session.
00:06:05 So it can only be saved if we fully commit the session.
00:06:08 This is for changing the title and the content, but now we have to figure out what happens with the tags.
00:06:13 Some tags might be added to a question and some tags might be removed.
00:06:19 So we can say const tagsToAdd is equal to tags.filter.
00:06:27 We filter each individual tag and we check if not question.tags.includes tag.toLowerCase, and that means that this tag doesn't yet exist in the original
00:06:43 tags array, which means that we want to add it.
00:06:45 In a similar way, we want to check some tags to remove.
00:06:49 By saying const tags to remove is equal to question.tags.filter, where we get each individual tag.
00:06:56 It can be of a type itag.doc, and then we check whether that tag doesn't include the tag.name In that case, we have to remove it from the list.
00:07:10 Finally, we can add those new tags, and we can do it in a very similar way of how we have done it in the create.
00:07:17 So actually, I will copy the part about adding or removing tags from here, from create, where we have this tag IDs, tag question documents,
00:07:27 and then a for-of loop right here.
00:07:30 Or you know what?
00:07:31 I see that this is a lot of code.
00:07:33 Let me actually go the longer route and let's code it together because there are some differences and I want to make sure that I point your attention to them.
00:07:40 First things first, we want to form a new array of new tag documents.
00:07:46 which at the start will be equal to an empty array.
00:07:49 Then if tagsToAdd array has more than one or more than zero things in it, that means that we want to add some new tags.
00:07:58 So for that, we can apply something similar to what we had before.
00:08:02 And you can even copy that part.
00:08:04 for const tag of tagsToAdd const existing tag is equal to await tag.findOneAndUpdate, where we try to find whether that tag exists based on the regular
00:08:17 expression of its name.
00:08:18 And then we set on insert the tag with that name, or we simply increment the number of questions belonging to that tag by one.
00:08:26 We upsert, we say new, and we attach it to a session.
00:08:31 Finally, if an existing tag exists, then we can say new tag documents dot push, where we push a tag equal to existing tag dot underscore ID to a question
00:08:43 to which we now have the access to ID coming to question ID directly from params.
00:08:48 Finally, we also want to say question dot tags dot push, and we want to push this existing tag dot underscore ID to the tags belonging to that question.
00:08:59 I know it sounds a bit complicated, but we are doing some logic where we're modifying different types of models and documents and make sure that they interact
00:09:07 with each other well.
00:09:08 Now, after we figure out which tags we want to add, now we want to figure out which tags to remove.
00:09:14 In a case where our user has previously added some tags like JavaScript to a question and then decides that maybe JavaScript is not the right tag,
00:09:22 maybe TypeScript is, so they removed it.
00:09:25 So we can say if tags to remove dot length is greater than zero.
00:09:35 In that case, we can say const tag IDs to remove is equal to tags to remove tag of a type.
00:09:47 I tag doc, and then give me back the tag IDs.
00:09:51 Next, say await tag.updateMany, like this.
00:09:58 And make sure that it actually says tag here and not tag question, because we're updating the tag itself, the model of a tag,
00:10:05 and not a model of a tag question.
00:10:08 So we want to update all of the tags whose underscore ID property is included in tags to remove because we're removing those and then we want to remove
00:10:20 the number of questions that that tag belongs to because it has been removed from a question.
00:10:27 Next, we want to remove the connection between the tag and the question by saying await tag question dot delete many.
00:10:37 So we can delete here because we're just removing connections, not actual documents.
00:10:41 And we want to remove each tag that is within the array of tags to remove belonging to a specific question.
00:10:49 And we'll attach a session to stop it in case something goes wrong.
00:10:53 Finally, we want to change the tags belonging to a specific question by saying question.tags is equal to question.tags.filter where we filter out not different tags,
00:11:04 but rather tag IDs.
00:11:06 So this is of a type mongoose.types.objectId.
00:11:12 And then we check it out if tags to remove includes this specific tag ID.
00:11:18 And finally, we want to exit this part of the code and say if newTagDocuments.length is greater than zero, so if there are some new tags,
00:11:30 In that case, await tag question dot insert many new tag documents belonging to a specific session.
00:11:38 So we're creating new connections to new tags that have been added to this question when it was edited.
00:11:44 It'll all start making sense once you actually see it in practice.
00:11:47 Finally, we want to await question dot save because we're finally saving this new question, the edited one.
00:11:55 And finally, we want to say await session.commitTransaction, and we can return an object saying success is true.
00:12:03 The data will be equal to JSON.parse, JSON.stringify, the question in hand that was edited.
00:12:10 And this is it.
00:12:11 This was our edit question action.
00:12:13 And alongside this action, we'll need another action to get the question details.
00:12:17 just so we can see whether we have edited everything properly.
00:12:21 So let me copy the starting part of this server action up to the part where we destructure some of the properties from programs.
00:12:28 And I'll create a new one.
00:12:31 This one will be called get question.
00:12:35 And for it, we'll need new params and a new schema.
00:12:38 So let's head over to our types, which is in types, action.d.ts.
00:12:43 And we can create a new interface, get question params.
00:12:49 Which will only take one thing, and that is the question ID.
00:12:52 We don't need anything else to create or change it.
00:12:55 We just need one to get all of its details.
00:12:57 So that's the question ID.
00:12:59 And in the similar way, we have to create Zod validation for it.
00:13:03 So let's head over to validations.
00:13:05 And let's say export const.
00:13:08 Get question schema is equal to z.object.
00:13:14 Where we get, I think you can guess it, a question ID of a type z.string that min one question ID is required.
00:13:24 Now, if we head back, we can change this to getQuestionParams as well as getQuestionSchema.
00:13:33 This one we have to import from validations.
00:13:35 And now we can properly close the function and finish this getAction.
00:13:40 This one will be a bit simpler because the only thing we're getting right here from params is the question ID.
00:13:46 Once we get it, we want to open up a try and catch block.
00:13:49 If something goes wrong, we simply return handleError as errorResponse.
00:13:55 And here we can try to fetch the question by saying const question is equal to await question.findById to which we can pass the question ID and populate
00:14:07 the tags.
00:14:09 If a question doesn't exist, we can throw a new error saying question not found.
00:14:15 And here we can finally return a success as well as the data being equal to json.parse.stringify question.
00:14:25 And now we have three different server actions, one that creates a question, one that edits it, and one that gets its details.
00:14:33 But there's one thing I want to discuss regarding this last one.
00:14:37 Even though it's the simplest one, it's quite controversial.
00:14:41 Like controversial how, you might ask.
00:14:43 Well, we're using a server action for fetching data.
00:14:47 But as you might or might not know, Next.js turns all server actions into post requests behind the scenes.
00:14:55 So how can a fetch request be turned into a post request?
00:14:58 Well, the answer is yes and no.
00:15:02 It depends on where you're using this action.
00:15:04 And to clarify this, I can say that server actions are designed to be used in different contexts.
00:15:11 In server components, they act like regular asynchronous functions.
00:15:15 So they're not turned into any kind of post requests.
00:15:19 But only if you use server actions inside of client components, then Next.js invokes them through post requests.
00:15:27 And why is that?
00:15:29 Well, it's called a direct invocation.
00:15:31 When you use a server action in a server component, you're directly calling the function of the server.
00:15:36 There is no HTTP request involved at all because both the server component and the server action are executing in the same environment.
00:15:45 But as I said, if you call it in the client side, Next.js has to figure out a way to call it.
00:15:50 So it turns it into a post request.
00:15:53 But in this case, it doesn't matter at all because whenever we are fetching data within our Next.js applications and hopefully all of yours in the future,
00:16:01 whenever you have a get request, you will not render it on the client side.
00:16:05 because there's no need to do that.
00:16:07 You can do that data fetching on the server side, which will be faster, more efficient, and it'll have many SEO and performance benefits.
00:16:16 So in the next lesson, let's test how all of these three server actions work and get questions on the client side.