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.
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:
- Aislar la lógica de negocio en una clase aparte
- Reutilizar fácilmente el código de trabajo con los datos a lo largo de varios controladores
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:
-
products/products.service.ts
para el código de la clase del servicio en sí. -
products/products.service.spec.ts
para el código de las pruebas unitarias.
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:
- El import de este servicio
- La declaración del servicio en el array de providers
@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:
- Importar el servicio que queremos usar
- Inyectarlo en el constructor de la clase
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) { }
// …
}
- Hacemos el import de la clase
ProductsService
- Creamos un constructor, que no teníamos hasta la fecha en el controlador
ProductsController
. - Dentro del constructor realizamos la inyección en los parámetros
- La palabra
private
permite que el objeto inyectado (el singleton deProductsService
) se asocie al controlador por medio de una propiedad privada. - La palabra
readonly
permite asegurarnos que el servicio es de solo lectura. Es decir, podremos operar con él y pedirle que haga cosas pero no se permitirá sobreescribir esa propiedad con otros valores. - La palabra
productsService
(con la primera en minúscula) es el nombre del parámetro, que también será el nombre de la propiedad creada. - Muy importante, para que funcione la inyección de dependencias, es indicar el nombre de la clase del objeto que estamos inyectando. Esto lo hacemos declarando el tipo del servicio que deseamos inyectar, en este caso
ProductsService
(con la primera en mayúscula). - Además, también es reseñable que en el constructor no tenemos código alguno, ya que el propio TypeScript gracias a la declaración
private
se encargará de generar esa propiedad privada por nosotros, sin que tengamos que escribir una línea de código.
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.
- Hemos definido una variable privada llamada
products
. Además la hemos inicializado ya con un array de dos objetos producto. - Hemos creado un método llamado
getAll()
que devuelve directamente el array de productos. - Hemos creado un método llamado
insert()
que introduce un nuevo producto, recibido por parámetro, en el array de productos.
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
});
}
}
- Dentro del controlador tenemos la propiedad
this.productsService
que es el singleton del servicio inyectado. - En el método
getAllProducts()
del controlador devolvemos directamente los productos del servicio, que conseguimos conthis.productsService.getAll()
- En el método
createProduct()
que es una ruta con el método POST, recibimos los datos del producto que se desea insertar y los enviamos al servicio con el métodothis.productsService.insert()
.
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...