Instructor: In this video, I want to, very quickly,
refactor the API features that we've implemented
over the last couple of lectures.
Now, this is not only to make our code a bit cleaner,
it's also to make it more modular and more reusable
in the future.
So, right now, we have all this code for the features
that we built before in this getALLTours function, right?
And this looks a bit messy.
It's a bit hard to understand, okay?
And, also imagine that we wanted to use these same features
for another resource.
For example, for the users or, later, for the reviews.
It would be not very practical to, basically,
copy the code from here and use it, then,
in the other resources, right.
And so, what I'm going to do is to, now,
create a class in which I'm going to add one method
for each of these API features or functionalities,
as you might call them as well.
All right?
So, let's go ahead and do that here
and I'm going to do it here, for now, then, later on,
I'm actually going to export it to its own file,
basically, to create a reusable module that we can,
later on, import into other controllers.
All right?
So, class, and I'm calling it APIFeatures.
All right.
Then, we start with our constructor function
and remember that this is the function that gets
automatically called as soon as we create a new object
out of this class, all right.
Now, what do I actually want in these API features?
Actually, I'm going to parse in two variables here, okay?
So the mongoose query and also the queryString
that we get from express.
So, basically, coming from the route, all right.
So that's what we usually have access to
in the req.query, okay.
Now, again, I'm passing the query here because I do not
want to query inside of this class because that would then
bounce this class to the tour resource but, again,
I want this to be as reusable as possible.
All right.
So, what we usually do in this constructor function
is to say this.query equals the query that we got
as an argument and then this.queryString
is equal to the queryString.
Okay?
So very simple, very typical constructor function.
And now, as I mentioned, I'm going to create one method
for each of the functionality, starting with filter.
All right.
And so, let's now go ahead and cut the code from here.
Or, actually, I'm going to copy it and them comment it,
so that I don't do any accidental damage.
So, copy and the comment
and put it right here.
So, a couple of things that we need to change.
First on, request.query is not going to be available
inside of this class.
And so, that's why we actually parsed in the queryString.
And so, this here is going to get replaced
with this.queryString.
Okay?
So, again, this is just basic Javascript.
Has actually nothing to do with Note or Express at all.
Then the rest here is okay but this here is not okay at all.
So I do not want to query the tour directly here, remember,
but instead, I simply want to now add this find
to the query that we already have.
So this.query.find
and so, that will then be this.
All right?
Let's, now, get rid of this.
And before we add any more methods here,
let's actually go ahead and use this class
just so this makes more sense to you.
Okay?
So, how is this actually going to work?
And I'm going to do it all here at the end here
under Execute Query.
All right?
And so, what I can do now is to create a variable
called Features, for example.
And then from here, I will create a new API features object.
Okay?
So, basically, creating an instance of this API features
that will then get stored into Features.
And this here, we'll, then, have access to all the methods
that we're going to define in the class definition.
Okay?
So, remember, in here, we need to pass a query
and the queryString.
So, the query, how do we create one?
Remember, it's Tour.find.
So, there's a query object and so, that's the one that
we parsed into this class and then, of course,
the queryString which is req.query.
Okay, now, on this features here, remember,
we have no access to filter.
And so, let's actually put it right here afterwards.
So, .filter.
Give it a save.
And so, just like this, we're going to run or code
for the API filtering functionality.
Then, here, the next line is, of course,
not going to work because this query here
does not anymore exist.
Instead, what we have now is features.query, right?
So after all this processing now, basically.
Okay, so, right now, after this filter, this.query
will then have this new find method on it.
Okay, and so, again, this is then stored in this.query.
And, later on, we will have all this other methods which,
all of them, will manipulate this.query
so that, in the end, this.query is the query
that we want to execute.
So, just like here.
Okay, so, just like here, we always kept manipulating
the query variable.
We kept adding more and more methods to it
until we, then, executed it by the end.
And so, here, we're doing exactly the same.
We are simply moving the code into all of these methods.
Okay?
Then, in the end, as I said, the query is going to be stored
inside of this property here and so, yeah,
that's where we, then, do the the await
and get back the results.
All right?
Anyway, let's keep moving here and implement the sorting.
So, I'm copying and commenting.
So, filter and the next one is called Sort.
All right.
And so, here again, we need to replace request.query
with this.queryString, okay.
And then, query is now this.query.
So let's get these four in this.query.
Okay, now, the goal here is to basically
chain these methods here one after another.
So let me show that to you here.
So we have filter and then, after that, we want to chain
the sort method.
Now, right now, that is not really going to work
because where are we actually chaining this sort on?
So, basically, we're trying to call it on the result of this
but, right now, what is the result of this?
Well, it's not really anything because this filter method
here doesn't return anything, right?
Now, this piece of code here, of course, returns
the object that has just been created and so, then,
we can chain the filter method on that.
But the filter method, in turn, does not return anything.
And so, at this point, we cannot really call a sort
on the object, right?
And so, the simple solution to that,
and maybe you've done it earlier sometime in your code,
is that we have to now return this.
So, return this and this is simply the entire object, okay?
And the same, then, down here.
So, return this.
In order to, again, return the entire object which,
of course, then has access to these other methods
so that we can, then, call them there.
All right?
So, next up is the limiting.
And I'm going to call this one limitFields.
And that's because all of these methods
will have these verbs as names.
So it's filter, sort, limit, and the paginate
is going to be the next one.
So, again, req.query is now this.queryString
and query here is now this.query.
All right?
Return this and this is not correct.
Okay, and finally, the pagination.
Copy, comment.
So, paginate.
Return this and, of course, we need to
replace these req.query
this.queryString.
And then here, just like before,
it's going to be this.query.
And I just realized that up there,
right in the first method, we didn't do this here.
So, we simply said this.query
and then added the find there.
But then, of course, we need to actually save it
into this query property.
Okay, and so, I forgot that.
And we got some error here.
All right, that doesn't really matter here
at this moment.
Or, actually, it does because this line of code here
is actually in our paginate method.
So let's go there.
And, in fact, I actually wanted to talk to you
about this here.
Because, if you think about it, requesting the next page,
which has zero result, is not really an error.
The fact that there are no results is enough for the user
to realize that, basically, the page that was requested
doesn't contain any data.
So we do not really need an error in this situation.
And so, I'm just going to go ahead and delete all this code.
Okay?
So let me save it now here.
And so now, of course, the error is gone.
And here, we also still have to keep adding
these other methods.
So, limitFields and .paginate.
Give it a save and, again, remember that all of this
chaining here only works because after calling
each of these methods, we always return this.
And this is the object itself which has access
to each of these methods here, making it possible
to chain them just like we have it here.
Okay, so, just to recap:
We are creating a new object of the API features class.
In there, we are parsing a query object
and the query string that's coming from express.
Okay?
Then, in each of these four methods here that we call
one after another, we, basically, manipulate the query.
We keep adding more methods to it just like we've been doing
up here before we did any of this refactoring, right.
So, we keep adding stuff to the query here until the end,
and then, by the end, we simply await the result
that query so that it can come back with all the documents
that were selected, okay?
And that query now lives at features
which is this object here.
So features.query.
Okay, I hope that make sense.
So, before we do anything else, let's go back to Postman
and actually try it out.
And we can try it out right here with this Top-5-cheap route
because that, actually, behind the scenes,
uses all of these API features, right.
So, if I send it, well, it still works.
So that's fantastic.
So let's close it and let me actually save it here.
So, Get Top Five Cheap Tours.
And yeah, I'm going to leave it there at the end.
And now, just here, let's do some testing.
For example, remove the sort, let me leave the limit.
Difficulty, duration, price.
So, I'm going to leave all of this here
just to see if it still works.
And it looks like everything is still working, right.
So these are the exact same results that we got
before our refactoring.
So, great, perfect.
That worked and so, let's, now, actually delete
all this code from here which makes our route handler
so much cleaner, doesn't it?
So, that's so much better, really, it's day and night.
Okay?
Now, the next step is to actually go ahead
and take all this code, cut it and put it into
a new file, so a new module, basically.
Okay, and for that, I'm going to create a new folder here
which I'm going to call Utils.
So that stands, basically, for utilities.
And I'm going to add a couple of stuff in here
over the rest of the course.
Okay?
So this one is going to be called
APIFeatures.js and with a capital F here.
All right?
So, here goes our class and, in the end,
we do a module.exports of this class.
So, nothing new at this point.
Give it a save, close it,
and now, in here, we can require it.
Const APIFeatures then require.
Then we need to move one folder up,
then into Utils and APIFeatures.
Okay?
One more test.
And it's taking some time, and here we go.
Beautiful, that's really just beautiful.
Now, if we create another resource again,
for example, for the users, it's going to be so easy
to drop in this same functionality.
All we're going to have to do is to require this file
and then, basically, do the same as here.
Or maybe, in that case, we don't even want
to have this sorting ability or we don't want
to have the filtering ability and then, all we have to do
is to simply take out this line of code.
And then, like magic, it'll only do
these three functionalities
or these three features, okay.
And so, that's really great.
I hope you can see the value of this
and, yeah, I'm really happy with this result.