The best way to do complex business logic computations for any API in Nestjs is to use a service Provider
class for the Controller
class of the API and delegate the complex computational tasks to the service Provider
class. The service Provider
class can be injected as a dependency to the Controller
class.
To make a class a Provider
or an injectable class, you can use the @Injectable
decorator function from the @nestjs/common
module and define it just above the Provider
class declaration.
TL;DR
File: greet.service.ts
import { Injectable } from "@nestjs/common";
// 1. `GreetService` service `Provider` class
// will be responsible for handling the complex business
// logic computation for the `/greet` API endpoint.
// 2. The `@Injectable` decorator function instructs Nestjs
// runtime to treat the `GreetService` class as a `Provider`
// so that it can be injected as a dependency to
// the `GreetController` controller class
// and use the methods of this class.
@Injectable()
export class GreetService {
sayHelloWorld() {
return "Hello World";
}
}
File: greet.controller.ts
import { Controller, Get } from "@nestjs/common";
// importing the `GreetService` provider class
import { GreetService } from "./greet.service";
@Controller("/greet")
export class GreetController {
// declaring and initializing the `GreetService` provider
// class using the fast and short way in the `constructor` method itself
constructor(private greetService: GreetService) {}
// The `Get()` decorator function will instruct the
// Nestjs runtime to use the `sayHello()`
// method for a `GET` of `/greet` endpoint.
@Get()
sayHello() {
// using the `sayHelloWorld()` method from the
// instance of the `GreetService` provider class.
return this.greetService.sayHelloWorld();
}
}
File: app.module.ts
import { Module } from "@nestjs/common";
import { GreetController } from "./greet/greet.controller";
import { GreetService } from "./greet/greet.service";
@Module({
imports: [],
controllers: [GreetController], // <- add controller here
providers: [GreetService], // <- add provider here
})
export class AppModule {}
For example, let's say we have a GET
API called /greet
that returns the string of Hello World
as the response.
The Controller
class for the /greet
API in Nestjs will look like this,
File: greet.controller.ts
import { Controller, Get } from "@nestjs/common";
@Controller("/greet")
export class GreetController {
// The `Get()` decorator function will instruct the
// Nestjs runtime to use the `sayHello()`
// method for a `GET` of `/greet` endpoint.
@Get()
sayHello() {
return "Hello World";
}
}
NOTE: To know more about creating controllers in Nestjs, see the blog on How to make a simple GET request or an API endpoint in Nestjs?
If you look inside the sayHello()
method of the GreetController
class above, you can see that it is returning a simple string of Hello World
. Let's imagine that returning the string is our complex business logic computation. Funny, Right? 😂.
Now let's move this logic to another class Called GreetService
which will return the string of Hello World
.
You may ask why are we moving this logic to another class from the GreetController
class.
It is due to the separation of concerns which is a better design pattern. Consider a case where the logic is too big then it would become hard for us to test the results of the function and becomes too coupled with the API endpoint itself. To remove this hassle we can delegate more complex business logic computation to a service Provider
class.
To do that first, we can make a class called GreetService
to define all the service methods related to the /greet
API endpoint.
It can be done like this,
File: greet.service.ts
// GreetService service `Provider` class
// will be responsible for handling the complex business
// logic computation for the `/greet` API endpoint.
export class GreetService {
// cool code here!
}
Now we need to define the GreetService
class as a Provider
so that Nestjs runtime knows that it is a Provider
class and can be injected into the GreetController
controller class. To do that, we can use the @Injectable()
decorator function from the @nestjs/common
module and write it just before the GreetService
class declaration.
It can be done like this,
File: greet.service.ts
import { Injectable } from "@nestjs/common";
// 1. `GreetService` service `Provider` class
// will be responsible for handling the complex business
// logic computation for the `/greet` API endpoint.
// 2. The `@Injectable` decorator function instructs Nestjs
// runtime to treat the `GreetService` class as a `Provider`
// so that it can be injected as a dependency to
// the `GreetController` controller class
// and use the methods of this class.
@Injectable()
export class GreetService {
// cool code here!
}
We have made the GreetService
class a Provider
. Now we need to make a method inside the GreetService
that returns the result of the complex business logic computation. In our case, it is the string of Hello World
. Haha 😅.
Let's make a method called sayHelloWorld()
inside the GreetService
that returns the string Hello World
.
It can be done like this,
File: greet.service.ts
import { Injectable } from "@nestjs/common";
// 1. `GreetService` service `Provider` class
// will be responsible for handling the complex business
// logic computation for the `/greet` API endpoint.
// 2. The `@Injectable` decorator function instructs Nestjs
// runtime to treat the `GreetService` class as a `Provider`
// so that it can be injected as a dependency to
// the `GreetController` controller class
// and use the methods of this class.
@Injectable()
export class GreetService {
sayHelloWorld() {
return "Hello World";
}
}
We have finally made our small GreetService
Provider. Yay 🥳!
Now we need to use the GreetService
provider class's sayHelloWorld()
method in the GreetController
class.
To do that we need to inject the GreetService
provider class as a dependency to the GreetController
class. We can do this by initializing the GreetService
class in the constructor
method of the GreetController
class. This process is called dependency injection.
A simple and fast way of doing the declaration and initialization of the GreetService
class inside the GreetController
class is by creating a private
variable with the type of the GreetService
class inside the parameters bracket.
It can be done like this,
File: greet.controller.ts
import { Controller, Get } from "@nestjs/common";
// importing the `GreetService` provider class
import { GreetService } from "./greet.service";
@Controller("/greet")
export class GreetController {
// declaring and initializing the `GreetService` provider
// class using the fast and short way in the `constructor` method itself
constructor(private greetService: GreetService) {}
// The `Get()` decorator function will instruct the
// Nestjs runtime to use the `sayHello()`
// method for a `GET` of `/greet` endpoint.
@Get()
sayHello() {
return "Hello World";
}
}
We have successfully injected the GreetService
class. Now let's use the sayHelloWorld()
method from the GreetService
provider class and replace the return value of the sayHello()
method of the GreetController
class.
To use the instance of GreetService
class which we have declared and initialized in the constructor, we can use the this
object which will have access to the GreetService
instance methods.
It can be done like this,
File: greet.controller.ts
import { Controller, Get } from "@nestjs/common";
// importing the `GreetService` provider class
import { GreetService } from "./greet.service";
@Controller("/greet")
export class GreetController {
// declaring and initializing the `GreetService` provider
// class using the fast and short way in the `constructor` method itself
constructor(private greetService: GreetService) {}
// The `Get()` decorator function will instruct the
// Nestjs runtime to use the `sayHello()`
// method for a `GET` of `/greet` endpoint.
@Get()
sayHello() {
// using the `sayHelloWorld()` method from the
// instance of the `GreetService` provider class.
return this.greetService.sayHelloWorld();
}
}
We have knitted together our GreetService
provider class and the GreetController
controller class together.
But this won't work as of now since Nestjs runtime doesn't have any context on the providers and the controllers.
For the Nestjs runtime to inject dependencies to appropriate classes (in our case GreetService
provider class for the GreetController
controller class) we have to add the controllers and providers in a referring Module
class.
If you navigate to the app.module.ts
(This is the default filename used if you have created the Nestjs project using the Nestjs CLI), there you need to import the GreetController
class and the GreetService
class and add it as a controller in the controllers
array and as a provider in the providers
array respectively in the @Module() decorator function.
It can be done like this,
File: app.module.ts
import { Module } from "@nestjs/common";
import { GreetController } from "./greet/greet.controller";
import { GreetService } from "./greet/greet.service";
@Module({
imports: [],
controllers: [GreetController], // <- add controller here
providers: [GreetService], // <- add provider here
})
export class AppModule {}
We have finally knitted everything together and created a provider class to delegate complex business logic computations for the /greet
API endpoint. Yay 🥳!
See the above code live in codesandbox.
To see the API response go to https://ni39xu.sse.codesandbox.io/greet.
That's all 😃.