DEV Community

Cover image for Introduction to NestJS Services
Santosh Yadav for This is Learning

Posted on • Updated on • Originally published at Medium

Introduction to NestJS Services

Service

In enterprise applications, we follow the SOLID principle, where S stands for Single Responsibility.

The controllers are responsible for accepting HTTP requests from the client and providing a response. For providing the response, you may need to connect to some external source for data.

If we add the code to connect to the external source inside, we are not following the single responsibility principle.

To avoid this issue, you use services, which will be responsible for providing some data, which can be reused across the application. It can also hold some validation logic or logic to validate users.

Creating and Using the Service

There are two types of services that can be created in NestJS:

  • Class-based Provider
  • Non-class-based Provider

Note: If you are coming from Angular, there are high chances you already know these concepts.

Class-based Provider

To create a class-based provider, we can use the CLI command below, the command will create the service inside the product folder.

nest generate service product
Enter fullscreen mode Exit fullscreen mode

In the product folder, you will find two files:

  • product.service.ts (For logic.)
  • product.service.spec.ts (For unit testing.)

You may end up using multiple services for a feature or even multiple types of providers.

Using a class-based Provider

Now open the product.service.ts file and add the below code, we will move some code from ProductController to ProductService.

import { Injectable } from '@nestjs/common';
@Injectable()
export class ProductService {
    products = [
        { id: 1, name: 'One Plus 7', price: 48000 },
        { id: 2, name: 'I Phone X', price: 64999 }
    ];
    getProducts() {
        return this.products;
    }
    addProduct(product:any){
        this.products.push(product);
    }
    getProductById(id:number) {
        return this.products.find(p => p.id === id);
    }
}
Enter fullscreen mode Exit fullscreen mode

As the service is ready now, open product.controller.ts and make the below changes.

import { ProductService } from './product.service';
@Controller('product')
export class ProductController {
    constructor(private productService: ProductService) {}
    @Get()
    GetProducts() {
        return this.productService.getProducts();
    }
    @Post()
    AddProduct(@Req() req: Request, @Res() res: Response) {
        this.productService.addProduct(req.body);
        // return json data with default status code
        return res.json({ id: req.body.id });
        // to update the status code
        //return res.status(205).json({ id: req.body.id})
    }
    @Get(':id')
    GetProductById(@Param() param: any) {
        return this.productService.getProductById(+param.id);
    }
}
Enter fullscreen mode Exit fullscreen mode

The way ProductService is used here is known as dependency injection.

Like Controllers, Services need to be registered as well, the CLI does this for us, you can do it manually by adding it to the providers array of the module.

providers: [AppService, ProductService]
Enter fullscreen mode Exit fullscreen mode

There is more about class-based services which we will cover in upcoming articles.

Non-class-based Providers

We can also create a service that is not a class-based service. There are two types:

  • Tokens: We can use string value as the token.
  • Factory: Useful when we have a service that needs some data from another service.

Creating tokens

You can create an injection token to use as service, to do that, create a new file product.token.ts inside the product folder and add the below code:

export interface Product {
    endPoint: string;
}
export const PRODUCT = 'PRODUCT';
export const Product_Token : Product = {
    endPoint: 'http://localhost:3000/product'
}
Enter fullscreen mode Exit fullscreen mode

Now open app.module.ts and register the token using the providers property.

import { PRODUCT, Product_Token } from './product/product.token';
providers: [
{
    provide : PRODUCT,
    useValue: Product_Token
}]
Enter fullscreen mode Exit fullscreen mode

Next, open the product.service.ts and let’s use this token and add the below code. This is just for demo purposes, in the real-time application we may want to use this value.

import { Injectable, Inject } from '@nestjs/common';
import { PRODUCT, Product } from './product.token';
constructor(@Inject(PRODUCT) product: Product) 
{
    console.log(product.endPoint);
}
Enter fullscreen mode Exit fullscreen mode

Once you run the application using the value, endPoint will be logged on the console.

Using factory

Factories are another type of provider and are available for a very special use case.

Generally, when we provide a service, they are resolved when the modules are loaded, but there may be instances where we need to create the instance dynamically, this is where we need factories.

For example, getting the database connection, for a client at runtime deciding which database to connect to.

Run the below commands to create two services:

nest generate service dbprovider
nest generate service client
Enter fullscreen mode Exit fullscreen mode

Add the below code in client.service.ts.

import { Injectable } from '@nestjs/common';

@Injectable()
export class ClientService {

    getClientDetails() {
        return {
            client: 'test',
            db: 'databaseconnection'
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Next, open dbprovider.service.ts and add the below code.

import { Injectable } from '@nestjs/common';

@Injectable()
export class DbproviderService {

    constructor(private connection: string) { }

    getProductsForClient() {
        return this.connection;
    }
}
Enter fullscreen mode Exit fullscreen mode

In dbprovider.service.ts, here we are using a string property, if you try to run this application, you will get the error as this is not allowed.

We want to create the instance of DbproviderService at runtime, so we need to make one more change. Open app.module.ts and remove DbproviderService from the providers property.

NestJS lets us create the factory, create a new file connection.provider.ts, and add the below code.

import { ClientService } from "./client/client.service";
import { DbproviderService } from "./dbprovider/dbprovider.service";

export const dbConnectionFactory  = {
    provide: 'ClientConnection',
    useFactory : (clientSerice: ClientService) => {
        return new DbproviderService(clientSerice.getClientDetails().db);
    },
    inject: [ClientService]
}
Enter fullscreen mode Exit fullscreen mode

Here we are creating a new instance of DbproviderService by getting db from ClientService. You can use multiple services here, you just need to pass them comma-separated in useFactory and the same services need to be added in the inject property.

Now we are done with the factory, let’s register and use it. Open app.module.ts and add dbConnectionFactory in the providers property.

Next, open product.service.ts and add the below code.

constructor(@Inject(PRODUCT) product: Product,
    @Inject('ClientConnection') dbProviderService: DbproviderService){
    console.log(product.endPoint);
    console.log(dbProviderService.getProductsForClient())
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

We learned about how to create and use different types of providers in NestJS, we used the dependency injection design pattern to use services, which lets you achieve single responsibility as well.

The services are singleton, but we can also control the scope of Services, which we will see in the next article.

Top comments (0)