
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
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 the implementation of a question creation feature within a web application, focusing on the functionality that allows users to ask questions, as well as manage associated tags. We detail the necessary steps to validate inputs, update or create tags in the database, and ensure all operations are successfully committed.
00:00:02 That was a lot of authentication, right?
00:00:05 But it wasn't just auth.
00:00:07 We dove into API routes, social authentications, as well as server actions, and a lot of best practices in structuring your codebase for error handling
00:00:17 and scalability.
00:00:18 But with that in mind, we're finally ready to dive into the core part of our application.
00:00:24 And that is the ability to ask a question.
00:00:27 We already have a form that does that, but now we'll implement its functionality.
00:00:31 So first things first, before we actually start, creating this code, I want you to think of the algorithm or the steps on how the creation will look like.
00:00:42 First, we'll need to take the question title, the explanation of the problem, and tags as inputs and validate them.
00:00:50 Next, we'll create a question in our database and associate it with the currently logged in user.
00:00:56 When it comes to handling the tags, we'll have to do it in a specific way.
00:01:00 If a tag already exists in the database, we increase the question count of that tag by one.
00:01:07 And if it doesn't exist, we create a new tag with the provided name.
00:01:11 After creating or updating the tags, we want to link each tag with the created question.
00:01:17 And for that, we'll use the tag question schema.
00:01:19 Remember, we designed the schema to keep the main tag schema lightweight by avoiding direct question references.
00:01:27 Think of a tag like JavaScript with thousands of associated questions.
00:01:31 It's better to manage this relationship in a separate schema.
00:01:34 Next, we have to update the question document to include the tag IDs that we just created or updated.
00:01:41 And finally, we want to save everything and ensure all operations are successfully committed to the database.
00:01:47 And that's it.
00:01:49 This, of course, is a bit more difficult than what it sounds.
00:01:52 But if we take it step by step, it'll all make sense.
00:01:56 Because when creating a question, we don't just have to create a question, but also create tags and other meta information associated with it.
00:02:05 So let's do that right away by creating a new file under lib, actions, and then we can create a new file called question.action.ts.
00:02:17 And since this will be a server actions file, you can immediately know to add the use server right here at the top.
00:02:24 Now we can create the star of our show, a server action that will be responsible for creating a question.
00:02:30 So let's say export async function, create question, And that's going to look something like this.
00:02:39 And I just want to let you know that this is exactly the same as const createQuestion is equal to an async function.
00:02:46 So either one of those two ways of creating a function is totally okay.
00:02:50 While I was doing the preparation for this course, I was checking out a lot of Next.js open source code bases, and I found this to be the most common way.
00:02:58 And that's exactly how I want to teach.
00:03:01 So that's exactly what I'll be using for this course as well to teach you the best and most common practices.
00:03:07 Now, as we discussed, this function has to handle different inputs, such as title, content, and tags.
00:03:13 So let's go ahead and create a type for that.
00:03:15 We can either create it here by creating a new interface called create question params.
00:03:24 And then we can accept a title as a string, content as a string as well, as well as tags, which is going to be an array of strings.
00:03:34 But this would just clutter our actions.
00:03:36 So let's actually pull it into a separate TypeScript file.
00:03:39 We can do it within types, action.d.ts and paste it right here at the bottom.
00:03:45 That way we can automatically use it right here, simply by saying params is of a type create question params.
00:03:54 And right here, we can also specify what this create question is returning.
00:03:58 It's going to be a promise, which ultimately will resolve in an action response, which is another one of our types.
00:04:06 So we know exactly how the response looks like.
00:04:09 Then we can validate the inputs that we get here by saying cost validation result.
00:04:15 is equal to a weight, and then we call our generic action handler, to which we can pass an object containing the params.
00:04:25 We can also pass a schema based off of these params will be validated, and that's going to be equal to ask questions schema coming from validations.
00:04:35 And we'll also pass the authorized property set to true.
00:04:39 This way we are ensuring that only authorized users are allowed to make a request, which is different from just trying to fetch all the posts.
00:04:47 Everybody can see the questions, but only authorized users can actually create them.
00:04:52 Next, we can check if a validation result is an instance of error.
00:05:00 In that case, we're going to return a handle error handler to which we can pass the validation result and say that this will be of a type error response.
00:05:13 Perfect.
00:05:14 And as we learned before, since this action involves interacting with multiple models multiple times, like creating questions,
00:05:22 tags, and then tag relations and more, we'll use Mongoose transactions to make sure that we have an atomic transaction, meaning either everything will
00:05:31 be created or nothing will.
00:05:34 There won't be a case that only a few models will be created, like the tags for a question, and then the question ultimately disappears.
00:05:41 No, that cannot happen.
00:05:43 So let's recap on how we can achieve that by saying const session.
00:05:47 And keep in mind, this is not an authentication session.
00:05:50 This is a Mongoose session.
00:05:53 So let's say mongoose.start session.
00:05:56 And then we can do the session.start.
00:06:01 transaction.
00:06:02 Of course, mongoose has to be imported at the top from mongoose.
00:06:07 That's going to look something like this.
00:06:10 And you can see that property start transaction does not exist on type promise client session.
00:06:15 Rather to start a session, we have to await it to get it.
00:06:19 So thankfully TypeScript comes to the rescue.
00:06:21 After that, we can open up a try and catch block and we can try to handle our error first by saying await session.abortTransaction because we want to cancel
00:06:34 all of them.
00:06:35 And we want to return handleError to which we pass the error as errorResponse.
00:06:40 And then finally, once everything is done, we need to end the session to make sure that all of the things we're creating actually get committed.
00:06:48 So now we can focus on the try block and we can do that by starting to create a question.
00:06:54 So let's say const, something right here, I won't say what yet, will be equal to await question, this is referring to the question model,
00:07:04 dot create, to which we have to pass an array, because we already talked about that caveat when it comes to creating documents and then passing a session
00:07:14 as the second parameter.
00:07:15 In this case, we can create only one question, so we can pass one object.
00:07:20 That'll have the title.
00:07:22 content, and author equal to the user ID.
00:07:26 And of course, these are coming from params.
00:07:28 So right here above, maybe we can do it just below the validation result because we then know they exist.
00:07:34 We can try to destructure the title, the content, and the tags coming from params.
00:07:42 Or specifically, it's going to be validation result.
00:07:47 And we know they'll be there.
00:07:49 We can also get the user ID here by saying const user ID is equal to validation result, question mark dot session, question mark dot user,
00:07:59 question mark dot ID.
00:08:01 So now we have both the ID of the user who is trying to create the question and then the question inputs as well.
00:08:07 And this will bring us back an array of questions that got created, but we only want to destructure the first question, so we get it as the first parameter
00:08:15 in this destructured array.
00:08:17 And let's not forget to pass the session as the second parameter, so we can cancel it if other actions don't go right.
00:08:24 Finally, if the question hasn't been created, so if there is no question, we're going to throw a new error failed to create a question.
00:08:32 But if that went through, we want to then get access to tag IDs by saying const tag IDs of a type mongoose.types.objectId.
00:08:46 specifically an array of object IDs, will at the start be equal to an empty array.
00:08:51 And we can also create tag question documents.
00:08:54 So tag question documents will be equal to an empty array at the start.
00:09:01 Then we want to loop over the tags.
00:09:04 And we can do that using the for of loop by saying for const tag of tags.
00:09:12 And then we can try to get a tag by searching for it in the database by saying const existing tag is equal to await tag coming from database tag models
00:09:26 dot find one and update you'll soon see why we're using the update part and the first parameter we pass is an object for the search criteria so we want
00:09:35 to search by name and we want to say that the name can be a regular expression so regex that's going to be a dollar sign regex will be a new regular expression
00:09:50 that's going to look something like this.
00:09:51 Let me actually pull it in a new line so you can see how that looks like.
00:09:54 So we're searching for a tag and we're searching for its name to be equal to regular expression where we have the carrot sign and then we're searching
00:10:04 for the tag and then a dollar sign right here.
00:10:09 We can also pass an I right here as a flag.
00:10:13 That'll be a string of I.
00:10:14 And if you want to learn what this regular expression does, well, regular expressions can always be a pain in the ass, but you can very easily figure that
00:10:22 out if you use some kind of a regular expression checker, or in this case, just using copilot or chat GPT will do.
00:10:29 So you can just take it here and say, what does this regexp?
00:10:33 do, and it says right here that the carrot sign asserts the position at the start of the string, the tag is the tag name being searched for,
00:10:42 the dollar sign asserts the position at the end of the string, and i makes the search case insensitive, which means that this will match any tag name that
00:10:50 is exactly equal to the tag string regardless of the case.
00:10:54 Then if we don't find one, we can set on insert And we can set the name of this new tag to be equal to tag.
00:11:04 And we can increment by using the dollar sign ink, the questions count or just questions to be one.
00:11:11 So we're setting it for the first time, the number of questions related to that specific tag, because later on we'll have some statistics on how many questions
00:11:20 there are with each tag.
00:11:21 And finally, we can pass some new options like absurd true and new true, as well as pass over the session.
00:11:29 So what we're doing here is if the tag doesn't exist, we insert a new tag with set on insert.
00:11:36 And if it exists, we simply increment the number of questions related to that tag by one.
00:11:41 And it looks like I have an extra closing curly brace here and one missing one right here.
00:11:47 So if I close it all properly, you can see that we're searching for a tag of a specific name.
00:11:53 And then if we don't find it, we insert the new one or we increment the count of those questions by one.
00:11:59 And then we get the existing tag because it must exist.
00:12:02 Either we just created it or it already has existed before.
00:12:07 So let's now run the tag IDs dot push where we push the ID of that existing tag.
00:12:13 We can also do the tag question documents dot push.
00:12:18 And we want to push over the tag, which will be equal to the existing tag dot underscore ID and the question equal to question dot underscore ID.
00:12:27 And the dot push is actually just pushing it to an array in our code.
00:12:31 But now we actually want to push it over to the database.
00:12:35 So for that reason, let's go below this for of, and let's say await tag question dot insert many, and want to pass over all the tag question documents
00:12:49 as well as the session.
00:12:50 What we did here is we inserted the tag question relationship documents in bulk within the session.
00:12:56 Finally, we also want to update the question to now include those tags by saying await question dot find by ID and update,
00:13:07 where we can search for the question based on the question dot underscore ID.
00:13:12 We can update the tags by saying dollar sign push.
00:13:18 tags, and we want to push each of the tags within tag IDs.
00:13:23 And finally, we have a session.
00:13:26 That's going to look something like this.
00:13:28 Finally, once we have created the tags and the question, we are ready to commit the transaction.
00:13:34 So await session.commitTransaction and return the necessary data to the frontend.
00:13:40 By saying return, success will be set to true, data will be set to json.parse, json.stringify and that's going to be the question.
00:13:52 And that's it.
00:13:53 This server action should allow us to successfully create not only the question, but also the tags associated with that question.
00:14:01 So in the next lesson, let's put it to the test.