
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 Let's start by defining a type of collection so we can define what kind of response this action will return.
00:00:09 So head over into the global.d.ts and define the interface for the collection.
00:00:17 It'll simply have an underscore ID of a type string, an author of a type string or author, and a question of a type question.
00:00:28 See how nicely we're using both of our author as well as our question interfaces within the collection interface.
00:00:36 Next, you'll want to head over into lib, actions, and then collection.action.ts.
00:00:44 And here you'll want to create and export a new asynchronous function called getSavedQuestions.
00:00:53 It'll accept params.
00:00:55 of a type paginated search programs, because we'll reuse all of the pagination, search, sorting, and filtering functionalities directly within get saved
00:01:06 questions as well.
00:01:07 Next, this function will return a promise, which will resolve in an action response.
00:01:13 And specifically that action response will contain an object that'll have access to a collection.
00:01:20 Specifically, it'll be of a type collection array and then the isNext property to let us know are there more questions in that collection of a type boolean.
00:01:30 And now we can open up a new function block.
00:01:32 First things first, we want to pass over the params and validate the result.
00:01:37 So I'll say const validation result.
00:01:40 is equal to await, we call our generic action call and pass over the params, pass the schema that we want to validate it against,
00:01:50 that is the paginated search params schema, and also pass authorized to true because we want to be logged in to be able to do this.
00:01:58 Next, we want to check if there are any errors.
00:02:00 So if validation result is an instance of error, then return handle error to which we pass the validation error as error response.
00:02:10 If there are no errors, we can extract some things from the params, such as the user ID is equal to validation result dot session question mark dot user
00:02:21 question mark dot ID.
00:02:22 And we also want to extract the page by default set to one, if no other values provided, as well as the page size by default set to 10. a query and a filter
00:02:35 from the validation result dot params.
00:02:38 Or I think we can just do params as well.
00:02:40 Next, for the search and pagination to work, we have to say skip is equal to, in parentheses, a number of a page minus one.
00:02:51 times the page size.
00:02:53 So if a page has 10 elements and we're in the second page, we want to skip the first 10 and only see the second 10. We also need a limit,
00:03:00 which will be equal to a page size.
00:03:02 Now we want to define sorting options based on some filters.
00:03:06 I'll say const sortOptions is equal to, or first of all, I'll define the type.
00:03:12 which will be of a type of record that'll have a string and a record that'll again be a string that can either be a number of one or a minus one.
00:03:21 It'll make sense soon, trust me.
00:03:24 We can open up an object here and say maybe something like most recent.
00:03:29 And here we can have an object that'll have a string of question dot created at is set to minus one.
00:03:37 So we're trying to fetch the most recently created questions.
00:03:41 What if we do oldest?
00:03:44 This will be something like question created at 1. Maybe like most voted, which will be something like question.upvotes minus 1. Or maybe like most viewed,
00:03:56 which will be question.views minus 1. or most answered.
00:04:01 So here we want to get the questions that have the most answers.
00:04:05 Now that we have created this sorting options, we want to create the sorting criteria.
00:04:10 const sortingCriteria or sortCriteria is equal to sortOptions and we want to choose a specific filter that will have been selected from the frontend.
00:04:23 And we pass it right here.
00:04:25 And this is a pretty cool thing in TypeScript.
00:04:27 We can see filter is a key of type of sort options.
00:04:33 So we can actually inherit the type of these sort options right here, or it'll be an object where questions created ad is minus one.
00:04:42 This is the default sorting selection.
00:04:44 After that, we can open up a new try and catch block.
00:04:49 In the catch, we can simply return, handle error, provide an error as error response.
00:04:54 We can do it in a single line like this.
00:04:56 And in the try, bear with me, we will have to do some pretty complex logic right here.
00:05:02 And let me tell you why.
00:05:03 See on the homepage, you can immediately filter specific questions or search through them because if you head over into the questions.action.ts,
00:05:14 And you look at it, we're still dealing with some sorts and filters, but all of the sorting and filtering that's happening is happening directly on the
00:05:24 question model.
00:05:25 Pretty simple, right?
00:05:26 Straightforward.
00:05:27 But with the collection, if we want to achieve the same search and filtering options, well, that'll be a bit tougher.
00:05:36 Why?
00:05:37 Because we're currently on the collection model, not on the questions model.
00:05:41 And that means that in this lesson, you'll learn a bit about some of the more advanced MongoDB functionalities, such as the infamous MongoDB aggregation pipeline.
00:05:52 Aggregation is a way of processing data to perform operations like filtering, grouping, or sorting.
00:06:00 similar to how you would use SQL queries for these operations.
00:06:03 And these so-called pipelines, you can think of as series of steps or stages that the data passes through, transforming it as needed.
00:06:14 It can include things like filtering, sorting, or performing different calculations.
00:06:19 So let's define our pipeline by saying const pipeline.
00:06:25 of a type pipeline stage coming from Mongoose, an array of pipeline stages, which will be equal to an array And first, we only want to match the questions
00:06:40 that were saved by the currently authenticated users.
00:06:44 So that's the first pipe or the first stage in the pipe where the author is equal to a new mongoose, make sure to import it,
00:06:53 dot types, dot object ID, and we can pass over the user ID.
00:07:01 Make sure to import Mongoose right at the top from Mongoose.
00:07:06 That'll look something like this.
00:07:08 Next, we want to perform a so-called lookup, where we can look into the questions collection to get the full question details.
00:07:17 So I'll say, give me the information from another collection, like from the questions collection, or let me say model, because coincidentally,
00:07:26 we called the saved questions a collection of saved questions.
00:07:29 So I'll be using the term model.
00:07:31 I'll say, give me more info from the questions model because we're currently in the collections model.
00:07:39 So I'll say local field in the collections model will be set to the question because question belongs to a collection.
00:07:47 And finally.
00:07:48 Which key will we match it with?
00:07:50 So foreign field will be the underscore ID and we want to store it as a question.
00:07:56 Now we can go two objects down and say $unwind and we want to unwind the dollar sign question.
00:08:05 This allows us to get a single object instead of an array.
00:08:09 We can continue going down to another stage in this pipe.
00:08:12 And we can once again perform a lookup.
00:08:15 You can perform lookups or unwinds or matches, however many types in the pipeline you want.
00:08:21 Pretty cool, right?
00:08:22 Not cool right now, I get it.
00:08:24 This is super confusing.
00:08:25 It might take some time to settle down.
00:08:27 But once you start understanding it, trust me, it'll be cool.
00:08:30 So now we want to look up from users, also define the local field of question.author.
00:08:39 Define the foreign field of underscore ID as question.author.
00:08:45 We can once again go two objects down and perform an unwind of the question.author to get them in a single object.
00:08:55 And finally, we can perform one last lookup, this time to get access to tags.
00:09:02 So I'll say from tags, local field question.tags, foreign field ID as question tags.
00:09:10 And this, my friends, is our pipeline.
00:09:14 We have to do this because we're working within a collection model and to be able to filter the data and search through it in the same way that we're searching
00:09:24 through it in the all questions.
00:09:26 where we're working natively within that specific model.
00:09:29 In the collections page, we're not fetching the questions by the questions themselves.
00:09:34 We're fetching the collections collection that then contains the question.
00:09:41 I'm trying to say this slowly as if it'll make you understand it better.
00:09:46 And because of that reason, we have to form this pipeline.
00:09:49 Finally, we can go down, exit this pipeline, and say if a query is provided, Then also add it to a pipeline.
00:09:57 This time we'll use a pipeline.push and I'll say match me or, so anything from this array, either based on a question dot title,
00:10:11 which will be a regular expression of a query with case and sensitive.
00:10:15 So we can search both uppercase and lowercase letters.
00:10:18 And we can also search by question content in the same way.
00:10:22 Finally, once we have pushed that last part of the pipeline, again, only if there is a query, we can now start retrieving those documents.
00:10:31 First, we need the total count of the questions in the collection.
00:10:35 So we'll say the structure, total count.
00:10:38 await collection.aggregate and we want to aggregate by spreading the entire pipeline.
00:10:49 And we just want to fetch the count of count.
00:10:53 This is used for pagination.
00:10:54 After that, we want to use the pipeline to apply different sorting options, such as push, sort based on the sort criteria,
00:11:03 skip it based on the skip criteria, limit it based on the limit criteria.
00:11:08 And we can create a final pipeline.push where we say project question is one and author.
00:11:19 is one.
00:11:20 Here, we basically tell it to return only the necessary fields.
00:11:25 Finally, we can say const questions is equal to await collection.aggregate, and to it, we want to pass the entire pipeline.
00:11:35 Basically, at this stage, we execute that pipeline and we fetch the questions.
00:11:41 So one more time to recap, the only reason why we needed to do all of this is because we're trying to fetch the questions,
00:11:51 not from the questions model.
00:11:55 If we do this, questions.getQuestions, that is super simple, right?
00:12:00 But...
00:12:01 Since we're trying to get the questions from a collection collection or collection model, we have to execute this entire pipeline that allows us to match,
00:12:12 look up, unwind, and get access to the questions by aggregating those different segments of a pipeline.
00:12:19 Finally, once we get the questions, we can also get the isNext property, which will be equal to totalCount.count is greater than skip plus questions.length.
00:12:33 And at the end, we can return an object where success is true and data is a collection, json.parse, json.stringify of questions and is next.
00:12:49 Our function is looking good.
00:12:51 It is a long one, I know, but hey, even if you don't fully understand what we just did right here, that is okay.
00:12:59 Just try to replicate it and try to understand what is happening.
00:13:05 If at a job interview later on, or maybe within some kind of a backend developer conversation about querying databases, a pipeline gets mentioned,
00:13:14 well, you will know what they're talking about.
00:13:18 It's an advanced term.
00:13:20 But in simple words, a pipeline is a series of steps that we aggregate together, which means that we pass the data through the stages,
00:13:30 transforming it as needed.
00:13:32 Match it here.
00:13:33 Give me some data.
00:13:35 Put it into this format.
00:13:36 Give me some more data.
00:13:38 If there's a query, do that as well.
00:13:41 And finally, we aggregate it, execute it, and we get back the questions in exactly the format we wanted them.
00:13:49 That's the only thing I want you to get out of this lesson.
00:13:52 Great.
00:13:52 So let's go ahead and push this.
00:13:54 I'll say implement pipeline aggregation for getting questions from a collection model.
00:14:05 commit and sync.
00:14:07 And in the next lesson, we can put that action to use.