Introduction to Nest.js Controllers
In this video, we're going to go over controllers in NestJS. In the previous video, we talked about creating, you know, maybe a NestJS API that allows you to manage an army of ninjas.
So in order to have an API, right, you probably will have some kind of routes defined. So let's write a couple comments here. Imagine that perhaps you want to have a GET /ninjas, right, which returns you maybe a collection of ninjas. Typically in REST, you might also have a route that allows you to get a single ninja where you provide an ID for that ninja on the path of your request.
You also want to be able to create ninjas, right? So typically in REST, it'll probably be the same endpoint as this, except we're using POST, HTTP POST. And then similarly, you might have a PUT or a PATCH on the /id path to update that record. And finally, you might have a DELETE, also taking in an ID parameter so that, you know, you can delete ninjas, you know, maybe you're trying to remove them from the battlefield.
So at a high level, controllers are in charge of defining exactly this, right?
Defining Routes with Decorators
It defines what are the paths, what are the HTTP methods for each of these paths. So you can see that in this controller, we have this string ninjas that basically says that everything within this controller is going to have that prefix, right? So that's one of the things that we want in our paths, is that ultimately each and every one of them has a prefix ninjas.
So in order to create these routes, again, you need a class that is annotated with the controller decorator and then optionally some kind of path that you provided in here. And then within your controller, you can define methods for each one of your routes.
So for example, if we were trying to do GET ninjas, let's define a method for that. Let's start with just returning an empty array. We'll update this later in the series. And right now, this is just a method for a class. Nest doesn't yet know that this is meant to represent a route. The way we do that is to include another decorator, Get from NestJS common, and that basically provides Nest an idea that hey, this method is to create an HTTP GET on /ninjas.
Now, you might be wondering, what about other paths that are beyond just ninjas? So if we were to implement get one ninja, and this is also a GET, but notice that in the definition of get, you can provide an optional path to append to the controller's path. So if we did ID like this, that pretty much defines this route.
Testing Endpoints and Scaffolding Methods
Now, let's go ahead and test this real quick to make sure it's working. Our application is already running. If you're following along and your application is not running, make sure to run npm run start:dev.
But we're back on Thunder Client. Localhost 3000, if we go to /ninjas, HTTP GET and hit send, we're going to get back that array. And if we were to pass any ID right now, we're going to get back that object, right?
And as an example, the next thing we need to implement is to POST on /ninjas. If we try to request that right now without it being defined, we should expect a 404 Not Found because it doesn't know where to bring that to. So just to save you a little bit of time, I went ahead and implemented the rest of that boilerplate for you, but it's all the same idea.
Notice that we got methods for each of these routes. There's a dedicated decorator for POST, PUT, and DELETE, and other ones that you might assume like, you know, PATCH. And then again, remember to provide the parameters inside the decorators here. And that's really routing in a nutshell.
Handling URL Parameters with @Param
Now, of course, there's a couple other parts here that we need to add in. For example, how do we get, how do we parse out this ID from the request so that our logic down here can work with it? So that's where the Param decorator comes into play. You usually provide this within the method definition here. And you can see that the parameter decorator can also expect a string. In our case, we're trying to parse this ID out of the URL. So we're going to say ID here. And then just for TypeScript, we're going to say that this is a string.
So what effectively happens here is that, you know, behind the scenes, Nest is parsing out the URL and auto-injects it into our method here when it gets invoked. So if we just print out perhaps the ID and response here, if we go back to our GET /ninjas/some-kind-of-ID, hit send, you'll see that the ID is the thing that we passed in, right? So you can probably imagine that we also need the same thing in our other routes down here. All right, so that's URL params.
Parsing Query Parameters with @Query
Now, the URL can also include a query for some of these, right? So for example, for ninjas, maybe you want to be able to filter the type of ninja that you're getting back, right? So your URL might look something like this with a question mark, type=fast. You know, maybe you have, I don't know, a fast ninja and a slow ninja, right?
So same thing, we need a way to be able to parse that out of the URL. And a lot of it is very similar to what we're doing here. Except instead of Param, we're going to use Query. And same thing, you can provide the string here that you're trying to parse out. Say this is a string. And again, just to kind of mock it out, let's provide the type that we inputted into the response. So if we did ninjas?type=fast, we should expect a response of, you know, it's got fast in there.
So I hope you see where we're going with this. Later on, we are going to do some kind of filtering on a real collection, but let's move on and learn other things about the controller.
Using @Body and DTOs for POST Requests
So typically with a POST request like this, right, you could think of it as you're creating a new ninja for your army. We need to be able to also parse the request body. And again, it's all the same pattern as you might imagine. There is a Body decorator that allows you to parse out that request body.
So maybe let's call this createNinjaDto. Now it would be nice if we had typing for this, right, so that we can kind of figure out what's the shape of this object. So what you typically would want to do, and notice that in our generated users resource earlier, it kind of did it for us. There's a DTO folder here where there's a create-user.dto and an update-user.dto. We're going to do something similar. Let's go ahead and actually just copy this into our ninjas folder so that it also has its own DTO folder. And then let's rename these to create-ninja.dto and update-ninja.dto. And let's also update the class name here to CreateNinjaDto. Same thing for Update.
All right. And you can see that this UpdateDto just extends the CreateNinjaDto. We're going to use this later in our update route. But let's focus on the CreateNinjaDto first, right? So imagine that maybe as you're creating ninjas for your army, maybe you want to have a name for each of those ninjas. And we can add more fields to this later, but for now we can take this and add a typing to our createNinjaDto in here. Make sure to import that.
And just to make sure that, you know, this is working correctly, let's go ahead and just create a mock created ninja here where the name is the same one that's passed through the DTO. Right? So if you go to our Thunder Client, let's go to POST /ninjas where we need to provide a request body with a name field. Give a name here, send, and it's going to give us back an object with the same name, right? So that just proves that we're able to parse this out. And later we're going to work with that and add validation. You know, if you keep watching the series, we'll come back to this NinjaDto to kind of improve it and add validation and all of other fun stuff.
Completing the Controller and Key Takeaways
Now to wrap up our controller here, similarly for an update route, besides the ID parameter, we also need its own request body there that provides the updates. So very similarly, we're going to add Body here with UpdateNinjaDto. And then we can provide our updateNinjaDto type, right? And same thing, we'll return the name.
Our controller right now is really not doing much, but you can see that its primary responsibility in NestJS is to really just define the routes. It's in charge of parsing things out of the request, from the URL, from the request body, and then ultimately for forwarding that down to services or providers, which will do a lot of the underlying logic that we want to actually happen. So think of controllers as very lean routers. They really don't do much other logic besides what we've done here.
Alright, so that pretty much wraps up our initial controller here. In the next video, we're going to talk about providers and dependency injection so that, you know, we can start using those services within these controllers so that we can start actually implementing these CRUD endpoints to behave as we expect them to.