> Manuales > Manual de NestJS

Qué son los servicios en Nest. Qué son los providers y cómo se puede inyectar estos tipos de clase en los controladores. Implementaremos un servicio nuevo, crearemos algunos métodos, para luego usarlo dentro de un controlador.

Servicios en NestJS

Los servicios son una pieza esencial de las aplicaciones realizadas con el framework NestJS. Están pensados para proporcionar una capa de acceso a los datos que necesitan las aplicaciones para funcionar. Mediante los servicios podemos liberar de código a los controladores y conseguir desacoplar éstos de las tecnologías de almacenamiento de datos que estemos utilizando.

Los servicios son clases, de programación orientada a objetos, como otros componentes de las aplicaciones. Son clases clasificadas como "provider", un tipo especial de artefactos dentro del framework. Por eso es interesante que expliquemos qué es un provider antes de ponernos a ver código de servicios.

Qué son providers

Los providers son un concepto que se usa en el framework Nest, para englobar a un conjunto diverso de clases o artefactos, entre los que se encuentran los servicios, repositorios, factorías, etc.

Los providers están preparados para ser inyectados en los controladores de la aplicación, y otras clases si fuera necesario, a través del sistema de inyección de dependencias que proporciona Nest. Gracias a la inyección de dependencias es muy sencillo crear y proporcionar instancias de las clases a los controladores, delegando al propio Nest ese trabajo de instanciar las clases.

Si quieres conocer de manera general este concepto de inyección, te recomendamos la lectura del artículo Inyección de dependencias.

Todos los providers que pensamos usar dentro de los módulos se tienen que declarar mediante el decorador @Module(). Esto no es un problema porque generalmente esta tarea se realizará automáticamente por el CLI al generar las clases inyectables. En este mismo artículo podrás aprender a usar el CLI para realizar esta labor y experimentaremos cómo nos ayuda a la hora de crear las clases tipo provider, en este caso servicios, y cómo el CLI las importa y declara convenientemente en el módulo.

Pero antes de ponernos a crear nuevos servicios podemos fijarnos que en nuestra aplicación inicial ya teníamos una declaración de providers en el módulo principal de la aplicación. La podemos ver en el decorator @Module del archivo app.module.ts.

@Module({
  imports: [],
  controllers: [AppController, ProductsController],
  providers: [AppService],
})

En esa declaración de "providers" tenemos un array donde encontramos el servicio usado para el ejemplo del "Hola Mundo".

Servicios en Nest

Ahora que tenemos claro qué son los providers y sabemos que los servicios son clases incluidas dentro del conjunto de providers, vamos a profundizar un poco en el concepto de servicio.

Un servicio tiene la responsabilidad de gestionar el trabajo con los datos de la aplicación, de modo que realiza las operaciones para obtener esos datos, modificarlos, etc. Es decir, un servicio tiene que ofrecer los métodos adecuados para que los controladores puedan realizar las operaciones necesarias para controlar los datos que necesiten usar.

Aparte de descargar de responsabilidad a los controladores y reducir el acoplamiento con las tecnologías de datos, gracias a los servicios podemos:

Construir un servicio

Para construir un servicio podemos usar el CLI de Nest. Para crear la clase de un servicio lanzamos el siguiente comando:

nest generate service products

Recuerda que el CLI tiene algunas abreviaciones que te pueden ahorrar teclear demasiado, de modo que ese comando sería equivalente a escribir:

nest g s products

Una vez construido nuestro servicio con el comando anterior podemos apreciar que se crearon los siguientes archivos:

Ambos archivos los situaron dentro de la carpeta src, como todo código propio de la aplicación.

Además también se realizó automáticamente la modificación del archivo app.module.ts, en el que se introdujo:

@Module({
  imports: [],
  controllers: [AppController, ProductsController],
  providers: [AppService, ProductsService],
})

Igualmente a los controladores, si no queremos que se genere el archivo de los test unitarios podemos escribir:

nest g s products --no-spec

Decorador @Injectable

Ahora vamos a prestar atención al código base de un servicio, donde es especialmente importante el decorador @Injectable.

Este es el código base que nos aparece en el archivo products/products.service.ts:

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

@Injectable()
export class ProductsService {}

El servicio de momento está vacío de funcionalidad, pero gracias al decorador @Injectable estamos creando una clase que será capaz de inyectarse en los controladores. Todo servicio debe tener ese decorador antes de la declaración de la clase que lo implementa, para poder usar la inyección de dependencias que Nest nos proporciona.

Un detalle importante del sistema de inyección de dependencias para providers es que las instancias proporcionadas a los controladores son únicas, es decir, existe una misma instancia del servicio a lo largo de toda la aplicación.

Este hecho de crear una única instancia de una clase responde a un patrón conocido que tiene el nombre de Singleton. Por tanto, podemos decir técnicamente que los servicios son objetos Singleton.

Cómo inyectar un servicio en un controlador

Los servicios se envían a los controladores por inyección de dependencias. Para conseguirlo en el controlador tenemos que hacer dos cosas:

En el proceso de inyección el propio constructor creará una propiedad dentro de la clase que contendrá el servicio inyectado. Es decir, asociará el servicio al controlador.

Todo esto lo conseguimos con un código como este:

import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put, Res } from '@nestjs/common';
import { ProductsService } from './products.service';

@Controller('products')
export class ProductsController {

  constructor(private readonly productsService: ProductsService) { }

  // …
}

Ahora ya estamos en disposición de usar este servicio dentro del controlador!

Implementando el servicio

Ahora vamos a ver cómo sería el código para la implantación de un servicio. Vamos a ir poco a poco, creando un código básico que nos permita hacer alguna cosa inicial, aunque luego, en futuros artículos, iremos ampliando funcionalidad al servicio y permitiendo el uso de bases de datos y similares.

Por tanto, en esta fase inicial del servicio vamos a usar la memoria como espacio de almacenamiento, lo que no es muy práctico porque cada vez que se reinicie la aplicación perderemos los datos.

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

@Injectable()
export class ProductsService {
  private products = [
    {
      id: 1,
      name: 'Vela aromática',
      description: 'Esta vela lanza ricos olores',
    },
    {
      id: 2,
      name: 'Marco de fotos pequeño',
      description: 'Marco ideal para tus fotos 10x15',
    }
  ];

  getAll() {
    return this.products;
  }
  insert(product) {
    this.products = [
      ...this.products,
      product
    ];
  }
}

En nuestra primera implementación estamos yendo a lo más básico posible. No estamos aprovechando todavía algunas de las ventajas de TypeScript que nos aporta el tipado, sin embargo es suficiente para comenzar a ver cómo podría ser un servicio.

Otro detalle importante, que destaca como carencia en este primer servicio sencillo es toda la falta de lógica de negocio. Los servicios son el lugar ideal para realizar todas las comprobaciones necesarias antes de realizar las operaciones. Por ejemplo, si me faltan tales datos para hacer un alta, o si para hacer una modificación requiero tener un usuario con los permisos adecuados. Poco a poco iremos complicando este código para incorporar este tipo de comprobaciones necesarias.

Cómo invocar los métodos del servicio desde el controlador

Ahora, para cerrar finalmente el ciclo de esta primera aproximación a los servicios, veremos cómo usamos este servicio desde el controlador. Básicamente lo que vamos a hacer es invocar los métodos que nos proporciona el servicio, para implementar las operaciones de listado de los productos y la inserción de nuevos productos.

import { Body, Controller, Get, HttpCode, Post } from '@nestjs/common';
import { ProductsService } from './products.service';

@Controller('products')
export class ProductsController {

  constructor(private readonly productsService: ProductsService) { }

  @Get()
  getAllProducts() {
    return this.productsService.getAll();
  }

  @Post()
  @HttpCode(204)
  createProduct(
    @Body('name') name: string,
    @Body('description') description: string
  ) {
    this.productsService.insert({
      id: this.productsService.getAll().length,
      name,
      description
    });
  }
}

Eso es todo, ahora puedes probar las rutas de get de todos los productos y del post para crear un nuevo producto para ver cómo los datos de productos gestionado por el servicio van aumentando.

Conclusión

En este artículo hemos realizado una bonita introducción de los servicios, componentes fundamentales de las aplicaciones Nest. Los hemos ubicado dentro de las clases tipo "providers", o proveedores, entendiendo las consecuencias derivadas, como poder hacer inyectables estos servicios para un fácil uso dentro de los controladores.

Luego hemos realizado una implantación muy sencilla de un servicio y lo hemos usado dentro del controlador, inyectándolo en el constructor y posteriormente usándolo en los métodos que era necesario. Tenemos un API ultra-elemental, con persistencia en memoria, que tiene mucho por delante para evolucionar, pero que empieza a parecerse a lo que nosotros queremos.

En el siguiente artículo vamos a abordar un tema interesante como es el de las interfaces, que nos permitirán explorar algo más sobre las ventajas de TypeScript para el desarrollo de aplicaciones en NestJS.

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual