
Join the Conversation!
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.
"Please login to view comments"
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.
Complete source code for this 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
##Looks like we found a thief monkey By the way, I liked the trick how you reached till here. You have a good sense of humor. You will improve a lot if you join our course with this passion.
var
(function-scoped, outdated)let
(block-scoped, modern and recommended)const
(block-scoped, cannot be reassigned)_
, or $
let let = 5;
is invalid)myVar
and myvar
are different)string
, number
, boolean
, null
, undefined
, bigint
, symbol
Objects
, Arrays
, Functions
Subscribing gives you access to a brief, insightful summary of each lecture to stay on track.
00:00:02 To implement a server action that allows us to handle the voting, let's first head over into the action.d.ts file, where we can define the params we need
00:00:13 to actually update it.
00:00:14 So let's create a new interface, which I'll call updateVoteCountParams.
00:00:23 And this one will get the target ID of a type string.
00:00:28 And let me explain what this target ID is.
00:00:31 So we're not calling it a question ID or an answer ID because votes can be added to both an answer and a question.
00:00:39 So we're going to have a target ID and the target type of either a question or an answer.
00:00:45 And finally, we need a vote type, either an up vote or a down vote.
00:00:53 Perfect.
00:00:53 Another cool thing we can do is also apply a change so we can know that it can either be 1 or minus 1. This is going to be for updating the vote count params,
00:01:04 but it'll be even simpler for creating the vote params.
00:01:08 So let me actually say interface create.
00:01:13 vote params.
00:01:16 And yeah, this will basically be the first three parts from here.
00:01:20 So I can actually put them there.
00:01:22 And then I can say that the interface update vote count params just extends the create vote params.
00:01:31 Okay.
00:01:32 So we have to know where we're applying that upvote or downvote to.
00:01:36 Is it a question or an answer?
00:01:38 And then we have to know, are we adding it or are we subtracting it?
00:01:42 Let's also head over into our validations, where I'll export const the create vote schema, which will be equal to z.object.
00:01:56 And we just have to define the same things once again.
00:01:59 The target ID will be of a type z.string.min1.
00:02:04 Target ID is required.
00:02:05 It looks like I misspelled it right here.
00:02:08 Then we have to define what the target type is.
00:02:11 So it'll actually be an enum of either a question or an answer.
00:02:16 And here we can also provide an additional message, something like invalid target type, if it's not a question or answer.
00:02:24 And finally, we need a vote type, which is also going to be an enum of either upvote or downvote.
00:02:31 and then we can create another one for updating the schema so we can say export const update vote count schema which will be equal to create vote schema
00:02:46 dot extend and we simply want to extend it with a change so i can say change will be of a type z dot number dot integer .min is minus 1 and a .max of 1. Perfect.
00:03:04 Now that we have all the proper typings, we can create a new file called vote action by heading over into lib actions and creating a new vote.action.ts.
00:03:18 Within it, we can first of all define this as use server because server actions can only be called on the server.
00:03:25 And then we can create a new asynchronous function, create vote.
00:03:31 which will take in the params of a type, createVoteParams, and it'll return a promise of an action response like this, as always,
00:03:44 and then we can open up a function block.
00:03:46 Within it, we can get access to the validation result by calling our all-powerful action handler to which we have to pass an object of params we pass into
00:03:58 this function, a schema that we want to validate the data against, in this case, the create vote schema coming from validations,
00:04:07 and also authorize set to true, which means that we have to be logged in.
00:04:12 Once we do that, we can check if a validation result is an instance of an error.
00:04:19 In that case, we can simply return the handle error handler and pass in the validation result as the action response.
00:04:29 If that is not the case, we can extract some data from the validation result by saying const, target ID, target type, as well as the vote type.
00:04:42 And that's going to be equal to validation result dot params.
00:04:48 And you can also add an exclamation mark at the end to indicate that we know that this data will exist there.
00:04:53 After that, we can get the user ID.
00:04:56 And it's coming from validation result dot session, question mark dot user, question mark dot ID.
00:05:04 Finally, if there is no user ID, in that case, we can just return handle error.
00:05:10 And specifically the error here will be an error of unauthorized.
00:05:16 as error response oh and here at the top we can also use the error response not action response then we can create a new mongoose session so const session
00:05:28 is equal to await mongoose.start session This is not related to the user session.
00:05:34 Rather, this is a session for us to atomically modify the votes.
00:05:40 Because if something goes wrong, we don't want it to update the database and then not handle that error.
00:05:45 We can only update the database if everything went through.
00:05:48 Let's import Mongoose at the top by saying import Mongoose from Mongoose.
00:05:54 And I will start a new transaction by saying session.startTransaction.
00:05:59 Now we can open up a try and catch block.
00:06:03 In the catch, we'll get access to an error.
00:06:06 And we want to simply abort the transaction and return handle error as error response.
00:06:12 And we also want to just end the session.
00:06:14 So I'll say session.endSession.
00:06:17 But now in the try, we can try to actually make it happen.
00:06:21 So I'll first have to try to find an existing vote on that answer or a question.
00:06:27 because we have to know whether our user has already voted or downvoted that specific post.
00:06:32 If they have done that, then they cannot do it again.
00:06:35 We only allow for one upvote and downvote.
00:06:39 That's one reason why we have to first find it.
00:06:41 And the other reason is, well, we have to know whether we want to take that upvote away or we want to give it in the first place.
00:06:49 So let's say const, existing vote is equal to vote.find.
00:06:56 And we want to find the vote where the author is equal to the user ID, the action ID is equal to the target ID, and the action type is equal to the target
00:07:09 type they're currently clicking on.
00:07:11 Let's also import this vote from add forward slash database.
00:07:15 And now we can also attach the fact that we are searching for this vote to our session by passing it like this.
00:07:22 Now we have to check if an existing vote exists.
00:07:28 If that is the case, we also want to check what is the type of the vote by saying if existingVote.VoteType is equal to the same vote type that the user
00:07:40 is currently clicking on.
00:07:44 if the vote type of upvote has been upvoted, and if they're clicking on that same vote type again, which is the upvote, then we want to do something.
00:07:54 So specifically, if the user has already voted with the same vote type, we want to remove the vote.
00:08:00 So we can say await vote.delete1.
00:08:05 And we want to delete a vote where an underscore ID is equal to existing vote dot underscore ID.
00:08:12 And we want to attach it to a session.
00:08:13 So if something goes wrong, we can stop that process.
00:08:16 And once we actually delete that vote, we want to update the vote count.
00:08:21 And for that, we'll use a whole another server action to handle it.
00:08:26 So one server action will call another.
00:08:30 To create it, I'll actually just copy everything we have here up to the point where we're getting all of these props from validation programs,
00:08:40 because this is more or less the same for every single server action.
00:08:44 And I'll add it right above.
00:08:46 Of course, let's properly close it.
00:08:48 And let's rename it to UpdateVoteCount.
00:08:54 This time, it'll be getting params of UpdateVoteCount params.
00:08:59 And alongside the update, it'll also get access to an optional session, which will be equal to ClientSession coming from Mongoose.
00:09:08 Then we're doing more or less the same thing.
00:09:10 We're passing the params to update the vote count.
00:09:12 But we don't need to authorize in this case.
00:09:15 We're already authorizing in the other server action, but the schema will actually be update vote count schema.
00:09:22 We check if we have any errors and then we extract all the information from it.
00:09:27 Here, we also have one additional param, which is the change.
00:09:31 Do we want to increment or decrement the number of upvotes or downvotes?
00:09:35 And we also have to figure out, are we leaving that upvote or downvote on a question or an answer?
00:09:41 So I'll say const model is equal to, if the target type is triple equal to question, in that case, we can make that model equal to question,
00:09:53 else we can make it equal to an answer.
00:09:56 And these question and answer are actual models coming from our database.
00:10:01 So this is pretty cool.
00:10:02 We've created a new variable called model, but it's going to be dynamic, either a question or an answer, depending on the target type of where we want
00:10:12 to attach the upvotes to.
00:10:13 We also want to figure out something known as a vote field.
00:10:17 So it's going to be either upvote or downvote.
00:10:20 And if vote type, is triple equal to upvote, then we can make the vote field equal to upvotes, else we'll make it equal to downvotes.
00:10:31 So let's open up a try and catch block.
00:10:35 In the catch, I'll just return a handle error, error as error response, And in the try, I'll try to actually update that specific model to increment or
00:10:47 decrement the amount of upvotes or downvotes.
00:10:50 We can do that by saying const result is equal to a weight.
00:10:56 model.findById and update to it.
00:11:02 We want to pass the target ID.
00:11:04 So this is either the question ID or the answer ID.
00:11:09 And we can also pass an increment property.
00:11:13 where we'll change the vote field.
00:11:16 Okay, so this is pretty cool.
00:11:18 Now we're dynamically changing everything.
00:11:20 The vote field is either up votes or down votes and we can pass in the change.
00:11:27 So we want to do it plus one or minus one and also we can apply a new is set to true and also pass in the session.
00:11:37 So if something goes wrong, we can stop it.
00:11:40 Let's make sure to properly close everything.
00:11:42 So here we're starting, we're opening one.
00:11:44 We want to open up an object right here.
00:11:46 We want to close everything right here, have a comma.
00:11:50 And then we finally have new to true and a session.
00:11:54 Oh, but I think I have one extra opening and closing brace because this curly brace was supposed to be here.
00:12:00 So if I remove it, we're good.
00:12:01 And finally, we can check if there is no result.
00:12:05 We can return, handle error, new error.
00:12:09 And we can say something like not not found, but rather failed to update vote count.
00:12:16 And finally, I'll return a success is equal to true if everything goes right.
00:12:22 Now I love this little block of code here.
00:12:25 It's one of my favorite little blocks of code in a long time.
00:12:29 And I know I'm a bit weird getting excited about small blocks of code right here, but this one is super, super interesting to me because it's so dynamic.
00:12:40 This block of code, or should I say this operation right here is super modular.
00:12:46 First of all, it can update either the vote count on the questions or on the answers, depending on the model that we put into this variable.
00:12:56 So it doesn't know.
00:12:57 It can be questioned that find by ID and update or answered that find by ID and update.
00:13:02 Also, we don't have to pass a question ID or an answer ID or something.
00:13:06 We just pass the target ID.
00:13:07 And then this is a pretty cool thing.
00:13:09 The vote field is also dynamic.
00:13:11 So not only can we choose which target we want to apply a vote to, we can also choose which type of vote we want to apply,
00:13:19 such as down votes, right?
00:13:21 And then we can say plus or minus or up votes.
00:13:25 And that is also dynamic, given the fact that we're passing this dynamic variable to it right here.
00:13:30 And the change is also dynamic, plus or minus.
00:13:34 So all of this is super, super cool.
00:13:37 Great.
00:13:37 So now that we have this updateVoteCount, we are ready to use it right here where we left off.
00:13:43 After we actually delete the vote from the database, we are ready to call updateVoteCount.
00:13:49 We can do that by saying await updateVoteCount.
00:13:54 And to it, we can pass the target ID, the target type, the vote type, as well as the change of minus 1. Why minus 1? Because here we are removing the vote.
00:14:08 Next, we can have an else.
00:14:10 What if the user hasn't already voted with a different vote?
00:14:14 In that case, we can simply update the vote, and that'll look something like this.
00:14:19 We can say await.
00:14:21 vote dot find by ID and update.
00:14:24 We have defined the vote to update.
00:14:27 So we'll pass in the existing vote dot underscore ID, pass in the vote type, and we can say new is true and pass in the session.
00:14:41 When structured, that'll look something like this.
00:14:43 So here we're updating the vote document within the database, and then we want to update the vote count.
00:14:49 So I'll copy this one, paste it here, but this time instead of minus one, we'll do one because we want to increment it.
00:14:56 And finally, we can provide an else to this if statement, not the inner one where we have the if and else, but rather to this outer one where no existing
00:15:08 vote exists yet.
00:15:10 If that is the case, we can create a vote for the first time.
00:15:14 So if the user has not yet voted, create a new vote.
00:15:18 And we can do that by saying await vote.create.
00:15:23 And since we're using this session, we need to pass an object of the document we want to create, and then we can attach it to a session.
00:15:31 And specifically in this case, we want to create a vote with the following information, the target ID, to know where we want to attach it,
00:15:39 the target type, question or answer, vote type, and then the change, in this case equal to 1, because we want to add that vote.
00:15:50 And finally, right below it, we want to do the same thing as before, and that is to update the vote count, this time with a 1. Oh,
00:15:59 and I think I forgot one thing from all of these update vote counts, and that is to actually attach them to a session.
00:16:06 So after this document, we can pass in a session that we want to attach them to.
00:16:12 So that is right here.
00:16:14 That is right here.
00:16:15 So if something goes wrong, we can abort all of those updates.
00:16:20 Perfect.
00:16:21 Finally, if we reach the end, then we are ready to say await session.commitTransaction and session and session, as well as return success to true.
00:16:33 Because if we reach this point, that means that we have properly updated, deleted, changed, or whatever the necessary voting logic.
00:16:43 And with that, we have a create vote and an update vote count server actions.
00:16:49 So let's export both of them so we can use them.
00:16:52 Let's not forget to commit.
00:16:53 I'll say implement votes server actions.
00:16:59 Commit and sync.
00:17:01 And in the next lesson, and in the next lesson, we'll put them to use.
00:17:06 Oh, and if you wouldn't mind, and if you found this little part of the code as cool as I did, and my detailed explanation and fascination about it,
00:17:14 you might want to leave me a review.
00:17:16 When you finish this lesson, there should be a modal pop-up that'll lead you to our trust pilot, where I'm collecting all of these good reviews.
00:17:23 And if you have something good to share, please do, I would appreciate it a lot.
00:17:27 Just let me know that aha moment or a discovery that I made you realize within this next GS course.
00:17:33 It really means a lot as we're trying to get to a 4.9 rating.
00:17:37 So once you do that, in the next lesson, let's put these two server actions to good use.