En este artículo vamos a ver la mejora que conseguimos al desarrollar con TypeScript gracias al tipado que podemos aplicar en los servicios Nest mediante interfaces.
En el artículo anterior hicimos nuestro primer servicio. Era un servicio de producto, realizado de la manera más básica. Ya advertimos que no estábamos aprovechando todas las posibilidades de TypeScript a la hora de implantarlo, entre otras cosas porque no habíamos definido el tipo "product" por ningún lado.
En este artículo vamos a ver cómo podemos mejorar el servicio valiéndonos de una interfaz de TypeScript, que nos permite especificar el tipo de datos de producto con sus propiedades. Esta ayuda acabará siendo patente en tiempo de desarrollo, ya que el editor nos podrá ayudar cuando surjan incoherencias de tipos, tanto de productos como de sus propiedades internas, o con el intellisense para tener que escribir menos y no olvidarnos de la estructura de esta entidad.
El presente texto será de utilidad para comenzar a conocer mejor las características de TypeScript si no las conocías anteriormente y como paso previo al aprendizaje de piezas fundamentales de las aplicaciones Nest como son los DTO o las Entidades de TypeORM. Pero debe quedar claro que que realmente el tipado de los datos no se realiza tal cual se explica en este artículo, sino que será por medio de clases que tendrán decoradores, las cuales nos facilitarán procesos extra como la validación de los datos. Puedes ver este artículo como un aprendizaje escalonado de utilidades más complejas que trataremos más adelante en este manual.
Si ya tienes nociones de TypeScript y sabes qué son las interfaces y cómo aplicar tipos en TypeScript, entonces probablemente te puedas saltar este artículo. Por el contrario, si eres nuevo en TypeScript o deseas aprender poco a poco, te vendrá bien para ir entendiendo las posibilidades del lenguaje y la aplicación del tipado estático.
Qué es una interfaz
Las interfaces en la mayoría de los lenguajes de programación orientada a objetos son una especie de clases abstractas que contienen únicamente declaraciones de métodos sin su implementación.
En las aplicaciones podemos crear clases que implementan esas interfaces, de modo que el compilador nos obligue a implantar todos los métodos de la interfaz para conseguir que esa clase no sea declarada como abstracta.
Cómo son las interfaces en TypeScript
Sin embargo, el concepto de interfaz, o interface, puede diferir de un lenguaje a otro. En TypeScript las interfaces son igualmente como clases abstractas, que carecen de implementación de métodos, pero que pueden tener propiedades. Las interfaces las podemos usar para definir tipos de datos heterogéneos en los cuales informamos de las propiedades que tendrá ese tipo y de los tipos de datos de sus propiedades.
En la práctica podemos usar una interfaz para crear un nuevo tipo de datos, que nos sirva para tipar más adelante las entidades que necesitemos y ser ayudados en el tiempo de desarrollo gracias a esos tipos.
Cómo definir una interfaz en Nest
Como otras veces, nos podemos ayudar del CLI para crear las interfaces en Nest. Las interfaces las podemos generar con el siguiente comando.
nest generate interface product
Esto crearía una interfaz en la ruta: src/product.interface.ts
. Sin embargo, quizás conviene algo de orden en el proyecto y un lugar como más apropiado para colocar esa interfaz sería la carpeta donde está el código de esa entidad.
Recordemos que tenemos una carpeta "products" que tiene el controlador y servicio de productos. Ahora podemos agregar dentro una carpeta "interfaces" donde coloquemos la interfaz o interfaces necesarias para definir el tipo de datos de producto.
Para que la interfaz se coloque en esta ruta, el comando sería el siguiente:
nest generate interface products/interfaces/product
Podríamos abreviarlo de la siguiente forma:
nest g interface products/interfaces/product
Código de la interfaz
Ahora pasemos a definir esa interfaz de producto, que va a ser bastante sencilla, pues de momento solamente hemos creado tres propiedades: identificador, nombre y descripción.
export interface Product {
id: number;
name: string;
description: string;
}
Cada propiedad tiene su tipo definido convenientemente, lo que nos ayudará bastante a partir de ahora.
Cómo usar la interfaz en servicios y controladores
Para poder sacarle partido a nuestra interfaz vamos a actualizar un poco el código de nuestro servicio y el controlador de producto.
Actualizando el servicio
El primer paso será actualizar el código del servicio, en el lugar donde declaramos la propiedad privada de sus productos. Ahora esa propiedad la definimos como un array de productos.
private products: Product[] = [
// contenido del array…
];
Por supuesto, tendrás que hacer el correspondiente import de la interfaz de producto. Posiblemente este import te lo genere el editor, pero no viene mal comentarlo.
import { Product } from './interfaces/product.interface';
Estupendo! es un pequeño gesto pero que nos ayudará bastante. Por ejemplo, imagina que te olvidas de definir la propiedad description de un producto, el editor lo alertará de esta manera:
O quizás por un error de tipeo escribes mal el nombre de la propiedad, también te lo marcará en rojo y podrás ver qué es lo que pasa.
Para seguir sacando partido a esta interfaz podemos tipar el valor de respuesta del método getAll(), que sería un array de productos.
getAll(): Product[] {
return this.products;
}
Y también podríamos tipar el parámetro del producto recibido en el método insert(), indicando que debe cumplir la interfaz Product.
insert(product: Product) {
this.products = [
...this.products,
product
];
}
Ahora, en cualquier lugar donde invoquemos estos métodos del servicio, podremos recibir ayudas si les pasamos parámetros de tipos incorrectos o podremos decirle qué tipo tienen los valores de respuesta.
Actualizando el controlador
También podemos aplicar algunos tipados en el controlador para conseguir darle un poco más de valor a la interfaz que acabamos de crear.
Por ejemplo, podríamos decir que el método getAllProductos va a devolver un array de productos:
@Get()
getAllProducts(): Product[] {
return this.productsService.getAll();
}
Es interesante ver que, gracias al tipado de todos los elementos de un programa TypeScript, el editor nos puede dar información certera de lo que tenemos en el servicio y los valores que esperan recibir los métodos:
Y por ejemplo, si me olvido de definir correctamente el valor enviado por parámetro a uno de los métodos del servicio, que habíamos definido de tipo Product, el editor me lo mostrará como un error:
Conclusión sobre el uso de las interfaces
En este artículo no tenemos mucho más que agregar. La idea era simplemente esta, mostrar cómo trabajar con las interfaces para definir los tipos de las entidades que estamos utilizando en la aplicación y poner en valor ese trabajo extra en las declaraciones de tipos con TypeScript, que puede significar una ayuda relevante a la hora de desarrollar en Nest.
Te quedaría a ti como práctica organizar un poco mejor el código del controlador y servicio para realizar las operaciones básicas del CRUD, completando algunos métodos que no hemos llegado a realizar hasta el momento. Este será el tema de nuestro próximo artículo, pero te sugiero intentar este ejercicio por tu cuenta antes de pasar a la lectura de las soluciones.
Queda nuevamente decir que en las aplicaciones de Nest trabajaremos con tipado de datos donde además agregaremos código para facilitar temas tan importantes como la validación o la definición de los datos de los modelos y su correspondencia con el esquema de las tablas de la base de datos. Dada toda esa funcionalidad que nos queda por agregar, en la práctica no usaremos interfaces habitualmente, sino clases, ya que necesitaremos aplicar decoradores, pero su aplicación será similar a lo que hemos visto aquí.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...