REST & GraphQL API Design in Node.js, v2 (using Express & MongoDB) Solution: Controllers & Responding
This course has been updated! We now recommend you take the API Design in Node.js, v4 course.
Transcript from the "Solution: Controllers & Responding" Lesson
>> Scott Moss: Hopefully you had enough time to work through this challenge and get some of those tests to pass. So remember, ignore the model specs and also you should ignore the query specs. The test you're gonna be looking for to pass are gonna be the route based specs, and we'll see what those look like in a minute.
[00:00:17] Let's just hop right into it. So you were tasked with filling out these functions down here. So again, these functions right here, because we're doing meta programming, meta programming as in like we're making code that makes code, right? These functions are going to be used to generate controllers for all of our resources, for all our models.
[00:00:40] So they're pretty generic, which is why they are functions that return functions and take in a model. So note that these functions are gonna be using every single resource, and that's why they're so generic. The other approach is what I did on v1, is where I went in, I wrote these controllers individually for every single resource.
[00:00:55] But ultimately, they're probably gonna be the same. To get around the fact that maybe we don't need this one controller to be so generic for this one resource, I allow you to extend it with overrides. So you can override it per model, if you want, it's totally fine.
[00:01:10] But let's just hop right into it. So for the createOne, basically, I told you, go ahead and assume that the controller methods are fleshed out, they're already there. I mocked them all out to return promises, so they should be fine. They all just return test data, all right?
[00:01:23] That's all they do, they don't call the database, we're not getting into databases yet. So for createOne, what we wanna do is we wanna say controllers.createOne. We satisfy the arguments. If you go look at the createOne method, it takes in a model, takes in a body. Right, the model is passed in, the body is on the request out body.
[00:01:40] It's the json body that's passed in from the request, right? So we get that, it's already been json parsed, so it's just an object at this point. We let this controller do its thing, we don't really care what it's doing. And then because we're creating one, that means this is probably happening on a post request.
[00:01:56] And on a post request, I'm assuming you want the resource back that you are requesting to create, right? So let's send back that resource, which in this case is going to be doc. The reason I put doc, because in Mongo, adoc is short for a document, and a document is like a thing you can save in a database.
[00:02:13] So if you go back to like relational databases, you have like tables, right? A table in Mongo is a model, basically, or a collection, and then a document is just an instance of that. So if we have a song model, then each song is a document. Does that makes sense?
[00:02:29] So this document is an instance of whatever model was passed in. So we take that document. We set the status to 201 because, technically, res http status(201) for a successful post and the test is checking for that, if you don't change it. Then, I just JSON the doc, right?
[00:02:49] And then I'm just handling an error here. I'm doing this next thing that we have not talked about. So if you did it, great, if you didn't, don't worry about it. We're gonna talk about that next, next [LAUGH].
>> Scott Moss: UpdateOne, this one is, there's two ways you can do this.
[00:03:06] And if you follow the clues you would have did it one way, if you didn't you just recreated your own way. So if you would have followed the clues you would have realized that updateOne is probably gonna be bound to a route that looks like this. So we'll have some like, let's not use user, /song/:id.
[00:03:21] I wanna do a put request to that. That's basically an update right there, right? It's a put request to some resource, and then the id of the thing that you wanna update. And then in the body is the stuff that you're trying to update. I wanna update the name to be new name, right?
[00:03:38] That's what that request looks like, right? So because it has a parameter on it, and we know if we go back to what we did before on the routers, we know that we have a handler that handles params. We can assume, and I told you this thing is eventually going to find the associated resource by that given id and attach it to the request, and send it along.
[00:04:01] We can assume that inside this updateOne, that that resource is going to be attached to the request. So if you put the clues together, you would have landed there. It's kinda hard to follow, but it's there. We'll talk about why it's called docFromId and how you can change that.
[00:04:17] But basically, you get that resource that was attached to the request from the params function. You get the update from the body. And then you pass it in to controllers.updateOne to satisfy it, which if you'll look takes the document to update, and then the actual update and it does nothing.
[00:04:34] So it does that, and then, again, this is also 201, send that back, catch the error, nixed it. deleteOne is very similar, because deleteOne also is the DELETE, followed by the resource, followed by the id of the thing you want to delete. There's no payload here, you just need the id.
[00:04:54] And you do the same thing. So that means that param's function is going to attach the found document for the given resource and attach it to the request. So because I already have that, I can just patch that in as well. And then it will do it's thing and then it will also delete it.
>> Scott Moss: getOne is pretty simple, because we know get is a request that looks like this, and because we're getting one, it also has an id. Get this one with this id, right, that's what that is. So that means it's also going to pass through our find by parameters function.
[00:05:29] And it's going to grab that document and attach it to the request and so forth and so forth.
>> Speaker 2: Why does it have to update?
>> Scott Moss: Yeah I guess it should just be just like everyone else. Let's just say docFromId, there we go. So getOne does that, passes it in, and I mean you already have it at this point so you really could have just returned it with JSON here.
[00:05:52] But I'm just trying to pass everything through the controllers, just in case we do something else there, which we may or may not. But I mean, it's literally already here, you can just respond, unless you're gonna do something extra, which, like I said, we might. Like for instance, if you wanted to do join tables, which Mongo doesn't support.
[00:06:08] But Mongoose uses this thing called population, which is like mocks join tables on the fly, then you'll do it there. So that's why it's great to just send it through just in case. But at the end of the day, this thing is gonna return this, it's gonna return the same thing by default.
[00:06:22] An then getAll, just literary, just gets all the stuff on this model. Give all the documents on this model, it's gonna give you back an array of documents, and you do the same thing. And then the one that's pretty much been used everywhere is this findByParams. So this one takes in a request, response, next, and it takes in a fourth argument.
[00:06:40] And that argument is gonna be whatever was on the parameter that you subscribed for. So for instance, if I go back to restRouter, I say, hey, look for this parameter. So if I look for a parameter called id, it's gonna pass that in as a fourth argument, right?
[00:06:53] And I can register for any parameter and how does it know there's a parameter id? Because I called it id down here. See that, I said hey, I'm making a route for /:id which is a parameter. When you get that id, run this function. And then that function is gonna do whatever it needs to do and then pass it down to wherever it needs to go.
[00:07:11] So if I did a request to slash, this is playlist. So if I say I wanna do a GET to /playlist/id, right? If I did a request to there what's gonna happen is, the first thing that's gonna happen it's going to execute this one right here. So it's going to get the param, or get playlist by id, so it's gonna do that first.
[00:07:41] Which is up here, that function's gonna do it. And that's because it actually came down here and was like, there's a /:id, id, I know what I'm supposed to do with id, I'm supposed to do this. It's the same name, if I change this to something else, it's not gonna do it.
[00:07:55] It has to be the same name. Right, if I had another parameter here, this is still only gonna run for id. The id that's passed in as a fourth argument, it's still only gonna be id, it's not gonna be thing. I would have to make another one for thing.
[00:08:09] To say this is what I wanna do with thing.
>> Scott Moss: So then once it does that, it gets the playlist by id, then that param function right here is going to send the control over to the original function that was going to run. In this case, a GET to /playlist/:id would be this one, so then it will execute this function.
[00:08:29] But by the time it executes this function, it already attached the document to the request. Before it passed the control over to this function, this function right here already attached the document to the request, which is whatever that playlist is by the id. Or if it didn't find it, maybe it threw an error, whatever you wanna do.
[00:08:50] So by the time we get here we already have it. And that's true for all these methods here. So any get, put, or delete for this route, whatever they execute, this function already attached the document to the request automatically for you. So that's what this function is right here, that's what it's doing.
[00:09:05] So you can see it takes the model, it takes the id, It finds that document on that model with that given id. Here's the document. If for whatever reason it didn't find the document, that means the id you got was wrong. So I mean you figure out what you'd wanna do, but I'm just gonna throw an error here, like we just didn't find anything with that id.
[00:09:21] If you did find it, you attach it to request, so request.docFromId, you can pick whatever you want. You pick whatever name you want. It doesn't matter as long as you don't override something that's already there like body or something like that. I picked a generic name, cuz it's the document from the id.
[00:09:37] If you were writing these controllers in each single resource individually, then you might put req.user, or req.song, or req.playlist. But because this is a generic controller for every single resource, I picked a generic name, docFromId. So that's how I can reference document from id up here, cuz it's being passed down from this function.
[00:09:58] This function runs first, and then when it calls next, which we'll learn about next, it sends control over to the next thing.
>> Scott Moss: And then you catch the error and you throw an error,
>> Scott Moss: That's it. So if you run this test,
>> Scott Moss: You should definitely see some fills.
[00:10:21] And you can ignore those, right, the models. But all the routes should be passing. So right here, /playlist should GET, POST the playlist.
>> Scott Moss: /user GET and POST, /song GET and POST, notice we're only doing getting posts for these resources, we're not doing, put and delete, and get all.
[00:10:40] And that's because we're gonna be writing tests later, and that's what I'm gonna have you all do. But yeah, those are the tests you wanted to see that were passing, ignore the model ones, and definitely ignore these right here cuz it's x'd out. When you see something like this in Mocha or Jasmine, that means the test was skipped.
[00:10:58] They didn't fail, they didn't pass, they were skipped. These tests got skipped. I should have skipped these too, but it's all good. So if you wanna learn how to skip a test, if anybody wants to figure that out, you can just put an x next to describe, it'll skip it.
[00:11:11] You can put it next to an it, it'll skip it. If you wanna just run one test only, you can do, where's it at?
>> Scott Moss: If you just wanna run one test, come here,
>> Scott Moss: You can just do .only. So you can come in and say I only wanna run this, so you say .only, and you run it again.
[00:11:41] It'll only run that test and all of its children, see? And then you're good. So that's very helpful for development. So you can be like, I only wanna run that and then I wanna watch it. And then now this test is always evaluating, so whenever you save your code, it's always watching it.
[00:11:56] I guess it just didn't want to watch it at all. Let's try that.
>> Speaker 3: Watch is a Mocha feature?
>> Scott Moss: Yeah, it's a Mocha feature. I guess it just didn't wanna watch. Well, I'm using web pack Mocha, which is a modified version of it, so it just doesn't wanna do that right now, for some reason.
[00:12:14] But yeah, you would watch it like that. Really good for development. Just keep the test running as you're writing your feature to make sure you didn't break anything. That way you don't have to like, write out this huge feature, now I'm gonna go back and test it, and everything broke.
[00:12:26] And you're just like, dang, where did it break, where did it break? Just keep it open, keep your tests executing. I know some of you all keep it running in your dock inside of your IDE. If you're using WebStorm or something like that, that's a good idea. You could do that in Atom and VSCode, too.