REST & GraphQL API Design in Node.js, v2 (using Express & MongoDB) Challenge: Querying MongoDB
This course has been updated! We now recommend you take the API Design in Node.js, v4 course.
Transcript from the "Challenge: Querying MongoDB" Lesson
>> Scott Moss: I'm gonna check out to lesson six. And then inside of here, if you go to modules, query.js, back into this file. Basically, what you are gonna be doing, is your gonna be trying to get the test to pass for the query. So if you run the test right now,
>> Scott Moss: You should get some failing test for the query, they're uncommented now, should be four of them. Two of them are already passing, cuz they're very naive tests. But four of them are failing, and we need to get them to pass. And we also need to make sure the ones that are passing are not falsely passing, which I'm sure they are right now.
[00:00:36] So basically, what you're gonna do is you're gonna come in here inside of this controller.
>> Scott Moss: The controller object. And you're gonna create the appropriate database calls for each action. I didn't show you how to do this yet, so we're gonna walk through some examples of each single one to show you how to use it with Mongoose.
[00:00:55] So let's talk about that for a minute. Let's just go, let me see, that's a document, that's a document, that's a document, that's a model. Okay, cool, let's do this. I'm just gonna come down here, blank canvas.
>> Scott Moss: And let's talk about using a model and getting a document from the database.
[00:01:16] And all the different ways we can do that using Mongoose. So let's say, I have a model in Mongoose, and in our case a model is a song, a user, or a playlist. Those are the only models we have, right? So let's just say we have a song, okay?
[00:01:30] I can use that song model, and let's say I have an id. I have an id from the parameter, and I want to find the song with that given id. Using Mongoose, I can say, song.findById. I could pass in that id like that. This is gonna return a promise.
[00:01:48] By default Mongoose uses their own implementation of promises. I converted it to use the native promises, so whatever the native promise has you have here, right? But actually what happens, is this thing actually returns a query what's called a monologue. Come back. It actually returns a mongo query object which has a promise on it.
[00:02:10] A query object, we'll talk briefly about it, but we won't really use it here. This allows you to drill down your query, kinda like SQL where you're like, I wanna findById, really, you wouldn't need a query. But let's say you did a generic find. I wanna find all songs where created at was this.
[00:02:26] If this is regex is true, the exact text match, this, that's the query objects. You wanna drill down on it. We're not gonna do that right now. So findById's pretty simple. But if you just want the pure promise, you can just go ahead and call it .exec, I recommend doing that.
[00:02:41] This means execute the query, that's what that means. So if you put .exec on anything that starts with find, so for instance song.findById, and then put .exec after that means there's no more query coming out there. For instance, I can come here and say .where, .limit,
>> Scott Moss: .whatever.
[00:03:02] When I do .exec, that means there's nothing else coming out there, that's what that means. It means execute this query, give me the promise. Because I'm not doing any query here, I'm just gonna jump straight to execute.exec. This returns a promise, and you can probably just go ahead, asynch await it, so I have a song here, there you go.
[00:03:21] So that would be a song.
>> Scott Moss: If you've never used async await, don't worry about it, just use a promise. If you're just like, what is that? It's cool, you can use promises, it's totally the same thing. I just like to use async await, cuz it's legit.
>> Scott Moss: So that's how you find one by id.
[00:03:40] Let's say, we want to find one, not by an id, but by name. So we'd say, Song.findOne. And then we'd pass in the query that we want. So we wanna findOne whose name equals thisname.
>> Scott Moss: That would give us that, and then we could also call .exec. And that's you would findOne by name.
[00:04:02] Or whatever property you wanna pass in here, whatever thing you wanna search by. But note, when you created your schema you didn't create any indexes by default. So when you start thinking about how you wanna search for data, this is just a data modeling thing. Make sure you're creating indexes on things you wanna search to keep your queries fast.
[00:04:23] If you know anything about indexes and databases, it's basically just a place where the database references properties and values so they can look them up faster, right? It's like a constant time to look up almost. It's a difference between having an array or an object. An object is constant time, right?
[00:04:40] It goes straight there whereas an array is linear. So the bigger the array, the longer it's gonna take to search. Constant time is O1 so it'll get it instantly
>> Scott Moss: So you got that. Let's say you wanted to create a song, all right, so many ways you can do that.
[00:05:01] But my favorite way is to use Song.create and then pass in the object that satisfies the song schema, right? So you can just do that. You can also do something like this.
>> Scott Moss: You can say, new Song and then you pass that in. And then afterwards you would have to call save to save it.
>> Scott Moss: That's how you create. And then, let's say you wanted to update. You could say Song.findOneAndUpdate.
>> Scott Moss: You pass in the thing you wanna find by. I wanna find the one with thisname, and I wanna update it with a new name called othername. Then I'm gonna pass in these options called new: true.
[00:05:48] New: true is important, it just means return the new updated document from this query, and not the one before I updated it. If you don't put new: true it will give you the old one before you updated it. If you put new: true it will give it to you after it updated it.
[00:06:01] That's probably what you want. You probably wanna get it after you updated it. So you gotta do new: true. I don't know why that's not a default. I don't know why you would never not want that. But, yeah, so that's how you find one and update. You can also do, as you probably guessed, findByIdAndUpdate.
[00:06:16] And you just pass on the id in here, instead of an object.
>> Speaker 2: The freeze and the findBy param we always just be finding and updating later?
>> Scott Moss: Right, so the next one is because, like you said, you're gonna be finding by param. So the param's gonna find it, and it's gonna pass the document along to the request object.
[00:06:40] So you need a way to update it, right? So the way you would do that is, all this was model level stuff. Let me just label that. This is all model level stuff. But I also can do the model. The updating stuff, like you're talking about, that's document level, and that's what we're gonna talk about next.
[00:06:57] So once you already have a document, as in from the params, then you can do this. So once you already have a document, like for instance, we have song, song is a document, here it is right here. What we can do is you can call .save on that if you want, and that will save it.
[00:07:12] So for instance if you had a song, you already have this song right here. And you're like you know what your name is now this. Your name = whatever this is now. And I wanna call song.save, and it's gonna save in database. This is async so you can await this.
[00:07:24] This also returns a promise. So that would give you the new song. So you basically just attach the properties you want to it. You can do merge, you can do whatever you want, and then whenever you call .save, that's gonna issue the right to the database. Because this actual object you get back is not just a regular object, it's a reference to a document that is live.
[00:07:44] It's a live reference. It's not a JSON thing. If you want this to JSON you would have to call .lean, which turns it to JSON. Or when you get it back you don't have to say song.to JSON or to object to cover it. But by default it's gonna be an actual live document that has a save method on it, a remove method on it, a populate method on it.
[00:08:06] It will have all those methods on it that it can use to act on itself.
>> Scott Moss: So that's how you would save an already gathered document, you would just call .save on it. So that's a document level thing, so you'll hit song.save,
>> Scott Moss: Right, so you're gonna use, let me see is there anything else you might use in here.
[00:08:36] You also have findOne and remove, findById and remove.
>> Scott Moss: And yeah, that's pretty much it, and this works for every single model in our database, right? It's all the same, so it doesn't really matter. So you're gonna take this. What we learn here about finding one, creating, finding one and update, removing, populating.
[00:08:58] And we're gonna fill out this stuff. So for instance, let's just walk through create one together. So createOne, takes a model, takes a body. Judging by those two things, what do you think we need to do?
>> Speaker 3: Model.create, model.create, hit return.
>> Scott Moss: Right, exactly, so we're gonna return model.create which returns a promise.
[00:09:21] And what do I wanna create? The body, exactly, that's it. You don't have to call .exec on create. .exec is for anything that starts off with find. So that's how you would create one. You just called model.create and you pass in a body, done. So updateOne, notice the name of the arguments, updateOne has a document, it does not have a model.
[00:09:40] This is a document. We talked about how to update documents down here.
>> Scott Moss: You put the properties on, you hit save. You can do a mix in, whatever you want. Put those properties on there, you save it. So notice that deleteOne is also a document. Documents know how to remove themselves, remember that.
[00:09:57] But I don't think the .remove returns itself. I don't know, can't remember how to figure that out. Document ToGet, the reason we're gonna do this right now because it's a trick question. You just literally just return the same thing and a promise so we can satisfy the promise API.
[00:10:19] It's cuz the document's already there. It's literally already there. This is just here, just in case, like I said, you wanted to do something extra like, I need to populate this thing first. Or I want to get rid of the id, if you wanted to do something with the properties on it.
[00:10:32] But at the end of the day, the params middleware already found it. So we're just returning what I found, so this just passes straight through. The getAll, that takes some model, so imagine what we have to do here. We have to find every single thing on this model, no parameters passed.
[00:10:48] And then the findByParam, takes a model and then a id. So you can imagine what it needs to do with that.
>> Scott Moss: So once you get all those, you should go to hit your test, and they should all pass.
>> Scott Moss: So I'm wanna leave this up right here.
[00:11:06] Basically, your goal is to get all the tests to pass. After this exercise, all your tests should be passing. So follow the test, read the test, you're gonna be working with these five functions right here, or six, I'm sorry, six functions. And all your tests should be passing.