Introduction to Nest.js Providers
In this video, we're going to talk about providers in NestJS. In the last video, we created a ninjas controller that defines our routes for our ninjas API. However, like I mentioned, it's very lean; it doesn't have much logic. And when we really think about where should that logic live, that's what providers or services are usually for.
Providers are really just a class, just like anything else in Nest, but they specifically have an injectable decorator. That means that this provider is something that can be injected into any class that depends on it. Now, what does that even mean? Let's maybe write some code first that I think will help explain this concept better.
So we mentioned that our API is meant to manage an army of ninjas. Now, that collection of ninjas probably lived somewhere, right? In a real application, you might save it in a database. In our case here, we're just going to create a simple array. So this array contains representations of our Ninjas. For the controller, you can probably assume that we have an ID, and we said that they also have a name from the create route. And then I also added a weapon property here just for fun, right? They might have ninja stars or nunchucks, whatever.
And then let's provide a method that simply returns this collection and it allows us to optionally filter by the weapon.
Manual Service Instantiation in a Controller
Now ultimately, we want to use that method in our getNinjas controller, right? So instead of filtering by type here, this probably should have been weapon, so let's update that query. And then instead of returning a mock array here, we'll return the actual collection of ninjas.
But first, how do we get that collection, or the service in general, into this controller? So if you weren't already familiar with NestJS, you might imagine that, oh, this is a class. That means that in order to get access to this method, I probably need to instantiate an instance of this class. So let's try that. Let's do const service = new NinjaService(), and then we'll return a service.getNinjas() pass in the optional weapon filter. By the way, notice that we have a type problem here because this is expecting a specific union of strings. So let's update this to be stars and nunchucks, and now it's good.
So we should be able to go to our Local Host again and do a request on GET /ninjas. Hit send there, and we should get back our array of ninjas. And we can go ahead and test our filter actually. Let's do weapon=stars, and that should limit it down to just the one ninja that has stars as the weapon.
Now, as you might imagine, as we start moving the logic for the rest of these routes into our ninja service, it might get kind of annoying to keep instantiating a new instance of the NinjaService across all of our routes. You know, it would be nice if that was just, you know, the instance was just created for us and injected into our NinjasController. And that is actually what Nest can do for you.
Understanding Dependency Injection
So what we're going to do in our NinjasController is we're going to add a new constructor here, and let's do private readonly ninjaService. We're going to provide this with the type of our service. Then we can actually comment this out and just replace this with this.ninjaService. And if you test that out, it should work the exact same way.
So what's going on here? Basically, what's happening is that because our service is a provider, right, it's an injectable, it's got this decorator up here, we're telling Nest that this is a class that, hey, you're in charge of instantiating this class, and you can automatically inject it to anything that depends on it. So we're saying that the NinjasController depends on the NinjaService, and you can tell Nest that by simply providing it as a parameter to the constructor. And Nest will look at the type of this and automatically sort of instantiate it for you behind the scenes, right? And similarly, we never instantiate the NinjasController class, right? Nest is doing that for us. So you can imagine that behind the scenes what Nest is doing is it's doing something like this. You know, it's doing something like that. And then you see that our NinjasController requires the NinjaService, right? So it's also doing something like this behind the scenes, instantiating that service and providing that service automatically into the controller.
And that's really what we're talking about in terms of injection, right? That's why the decorator for our provider here or service is called @Injectable, because behind the scenes, NestJS is automatically instantiating it for you and injecting it to your controllers and anything else that is depending on it. And you know, you can inject a provider into a service, right? You can use multiple services that inject other services. Controllers can inject multiple services. It's really up to what you're trying to build. But understand that at a super high level, this is what's happening behind the scenes. This is dependency injection.
So basically, you need to remember that you shouldn't pretty much ever instantiate classes in NestJS. A lot of it will just happen automatically for you.
Implementing CRUD Logic in the Service
Alright, so from here, what we really need to do is let's start to implement the logic behind the rest of our ninja CRUD into our ninja service. So we probably want a method to find a single ninja. That's pretty easy. Let's just do a getNinja that takes in a number ID and tries to find that from our collection. And then we'll have a special case of what if we don't find a ninja? We'll throw an error here, and we'll talk about exception handling probably in the next video. But for now, throw the error, and then otherwise we'll just return the found ninja.
Next, we probably want to be able to create ninjas. And we said that we already have an object that represents, you know, the thing that we pass in. So let's do createNinjaDto. We'll just reuse that here, and we're simply going to add to our collection of ninjas. However, we have a type error here because we have a couple things that are missing on our object. One is that we said we're providing weapons, right? So we need to update our CreateNinjaDto to have the weapon. And then next, this is still expecting an ID, so we need to generate an ID. So let's do something like this, right? So we'll just generate an ID for that ninja, and then we'll pass it in into our collection and return that ninja.
Now, just to save you some time, I went ahead and actually implemented the updateNinja method in our service as well as the removeNinja. Go ahead and pause the screen if you need to copy it, but basically we're doing this.ninjas and replacing it with a map of the original collection, simply overwriting the ninja with the same ID as the thing that we passed in. And then remember, similarly as the CreateNinjaDto, we have the UpdateNinjaDto. So that's how we're sort of doing the update of the original record. And then we're simply returning—we're using the getNinja(id) here so that we get back that single item in the collection. And then for removeNinja, similarly we query that ninja that we're looking for, and then we update our collection to basically filter it out, and then we return that ninja that we found, you know, just prior to it being filtered out.
Connecting the Controller to the Service
Now we need to remember to go back to our controller and update the rest of these. So for getOneNinja, this should return this.ninjaService.getNinja(), passing the ID. Now notice that we have a type error here because it's expecting a number, not a string, and coming from the URL, it is a string, technically. So we'll typecast this by adding the plus in front of it, and that basically turns it into a number.
For createNinja, same thing. We do this.ninjaService.createNinja() and then we just forward the DTO. Again, same thing, pass in the number ID and the update DTO. And for delete, let's wrap this up. Alright, so that pretty much wraps up our basic implementation of a CRUD API, right? We got our read, create, read, update, and delete.
End-to-End API Testing and Next Steps
Let's go ahead and test our application so far. So again, if we do a GET on /ninjas, we have, as before, our two ninjas. And we should be able to query a single ninja by providing the ID up here. Let's try adding a new ninja. So remember that needs to be a POST request on /ninjas, and you need to provide a request body. We said that our CreateNinjaDto requires a name. We'll call this ninja C, and a weapon, or maybe this ninja just has a stick for a weapon. Let's hit send. We get back a 201 Created response with that name and that weapon and a new ID. That means that if we provide this as a parameter to the URL and do a GET, we should be able to get that same ninja back, right, that we just added to our collection.
And similarly, we can go and update this ninja by changing their weapon to stars. Let's hit send here. PUT request on ninja/:id. And we get back the updated record. So just to make sure we're doing this correctly, if we do a GET on /ninjas, we should have the three ninjas, and then the last one should have a weapon of stars because we just updated it.
Although, you might catch that we have a problem, right? Because our application kind of describes that there's only two weapons—there's only stars or nunchucks. But we were able to add a ninja with a weapon of stick. So ideally, there's validation there, right? So we'll cover that in the upcoming video. But let's keep moving and testing our application for now. Let's go ahead and test deleting this ninja to make sure that our last route is working as expected. So we'll do a DELETE on /ninjas/:id. We don't need a request body in this. Hit send, and it's going to respond with the deleted record. Again, just to make sure that we're doing it correctly, let's go to /ninjas and do a GET to make sure that it's no longer in our collection of ninjas, right, our army.