Cuando hablamos de la utilización de JavaScript en el lado del servidor, rápidamente pensamos en Node y otros frameworks que casi siempre lo acompañan y que nos ayudan en el proceso como puede ser Express. NestJS está dentro de este grupo de frameworks, pero no es uno más, ya que tiene características que lo diferencian del resto.

¿Cuáles son las características de NestJS que lo hacen tan ‘especial’?

Si desgranamos un poco este framework vemos que está basado en Node y Express. Además, nos permite construir un backend en TypeScript (o JavaScript) con el patrón MVC.

De hecho, aquí se encuentra una de sus principales fortalezas, ya que ofrece un poderoso marco de trabajo similar a otros frameworks MVC existentes en otros lenguajes, como puede ser Spring MVC en Java.

Destacando algunas de sus características, cuenta con lenguaje tipado, separación por módulos, generación automática de entidades a partir de una base de datos, ORM, construcción de endpoints a través de decoradores, inyección de dependencias y soporte a Swagger.

Otro aspecto que lo hace ser un framework atractivo para un desarrollador frontend es que NestJS está influenciado en gran medida por Angular, aprovechando muchos de sus conceptos como son los módulos, los controladores e inyección de dependencias.

En este sentido, también cuenta con su propia CLI para generar y facilitarnos las tareas de creación de todos estos elementos.

Instalación CLI

Para hacer más sencillo el proceso de construcción y desarrollo del proyecto podemos utilizar Nest CLI. Si disponemos de npm, solo habrá que ejecutar el siguiente comando para instalarlo:

$ npm i -g @nestjs/cli

Creación de proyecto

En este caso, vamos a crear un API Server que nos va a devolver gatos (nombre, edad y raza).

Aclarar que se trata de un ejemplo básico de iniciación para conocer la estructura y la lógica que sigue el framework.

Lo primero que hacemos es crear el proyecto con el siguiente comando:

$ nest new cats-app

Veremos que se nos ha generado un proyecto con la siguiente estructura:

Si observamos la imagen, vemos que crea una estructura parecida, por no decir casi idéntica, a la que genera el CLI de Angular cuando iniciamos un proyecto nuevo.

Tenemos un archivo main.ts, que es el encargado de la entrada de la aplicación. En él se crea una instancia de la aplicación Nest y se establece el puerto en el que se va a ejecutar la aplicación. También nos encontramos con un archivo app.module.ts, que es donde vamos a tener nuestro módulo raíz de la aplicación.

Creación de los componentes necesarios

Cuando creamos el proyecto mediante el CLI, por defecto, se crea un controlador y un servicio con el nombre app implementando el típico “hola mundo” de ejemplo. Vamos a crear otro módulo, controlador y servicio para este ejemplo.

En este caso, al tratarse de gatos, nombramos como cats a nuestro módulo. Para crear el módulo ejecutamos:

$ nest generate module cats

CREATE /src/cats/cats.module.ts (81 bytes)

UPDATE /src/app.module.ts (308 bytes)

Si nos fijamos, se ha actualizado el fichero app.module.ts, que es nuestro módulo raíz. Lo que se ha hecho en este proceso ha sido importar el módulo que hemos creado al módulo general de la aplicación:

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

import { AppController } from './app.controller';

import { AppService } from './app.service';

import { CatsModule } from './cats/cats.module';

@Module({

 imports: [CatsModule],

 controllers: [AppController],

 providers: [AppService],

})

export class AppModule {}

Ahora creamos el controlador:

$ nest generate controller cats

CREATE /src/cats/cats.controller.spec.ts (479 bytes)

CREATE /src/cats/cats.controller.ts (97 bytes)

UPDATE /src/cats/cats.module.ts (166 bytes)

En este caso, el CLI actualiza el módulo creado en el paso anterior, CatsModule y le indica qué controladores forman parte del módulo, por lo que se añade el controlador de CatsController que acabamos de crear.

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

import { CatsController } from './cats.controller';

@Module({

 controllers: [CatsController],

})

export class CatsModule {}

Y por último creamos el servicio:

$ nest generate service cats

CREATE /src/cats/cats.service.spec.ts (446 bytes)

CREATE /src/cats/cats.service.ts (88 bytes)

UPDATE /src/cats/cats.module.ts (240 bytes)

En este caso también actualiza el módulo CatsModule. Lo que hace es indicarle qué providers va a necesitar, añadiendo en este caso el servicio CatsService que acabamos de crear.

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

import { CatsController } from './cats.controller';

import { CatsService } from './cats.service';
@Module({

 controllers: [CatsController],

 providers: [CatsService],

})

export class CatsModule {}

Antes de empezar a crear endpoints y añadir lógica, creamos dos elementos que vamos a necesitar. Uno de ellos es una Interface de cat y otro de ellos es el DTO para la creación del objeto tipo Cat.

Primero generamos la Interface:

$ nest generate interface interfaces/cat

CREATE /src/interfaces/cat.interface.ts (24 bytes)

Para este ejemplo solo contaremos en la Interface con el nombre, edad y raza del gato:

export interface Cat {

 readonly name: string;

 readonly age: number;

 readonly breed: string;

}

Después creamos el DTO por consola, pero sin CLI. Para ello, creamos el directorio src/dto y después el fichero create-cat.dto.ts.

$ mkdir src/dto && touch src/dto/create-cat.dto.ts

A continuación, en el fichero create-cat.dto.ts definimos la clase CreateCatDto y le asignamos los atributos que va a tener. En este caso, al tratarse de gatos, serán también un nombre, la edad y su raza.

export class CreateCatDto {

 readonly name: string;

 readonly age: number;

 readonly breed: string;

}

Implementación de un servicio

Vamos ahora a implementar dos métodos en nuestro servicio: uno que nos permita crear gatos y otro que nos permita recuperarlos. Para ello, importamos nuestra Interface de Cat en el servicio y definimos un array de tipo Cat.

Después definimos un método create que recibe un parámetro de tipo Cat y que se ocupe de almacenar en el array los gatos creados. Para este ejemplo no se va profundizar en almacenar esta información fuera de la aplicación, pero se podría conectar por ejemplo con una base de datos.

Para finalizar la implementación del servicio, definimos un método findAll que nos devuelva el array con la información de los gatos almacenada.

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

import { Cat } from '../interfaces/cat.interface';

@Injectable()

export class CatsService {

 private readonly cats: Cat[] = [];
 create(cat: Cat) {

   this.cats.push(cat);

 }

 findAll(): Cat[] {

   return this.cats;

 }

}

Implementación de un controlador

Ahora vamos a la parte del controlador. Importamos en el controlador la interfaz Cat, el DTO CreateCatDto y el servicio CatService.

Añadimos una instancia del servicio en el constructor para poder utilizarlo en el controlador. Después definimos nuestros métodos de crear y de buscar gatos.

Para el método create, indicamos a través de la etiqueta decoradora el tipo de acción que realiza, en este caso se trata de tipo POST. Por parámetros le indicamos que va a recibir un objeto de tipo @Body, que a su vez es el que vamos a enviar al servicio para crear el objeto tipo Cat.

Por otro lado, en el método findAll, le indicamos que vamos a devolver una promesa de array de tipo Cat. En este caso, solo debemos hacer un return de la llamada al servicio del método findAll.

import { Body, Controller, Get, Post } from '@nestjs/common';

import { CatsService } from './cats.service';

import { CreateCatDto } from '../dto/create-cat.dto';

import { Cat } from '../interfaces/cat.interface';
@Controller('cats')

export class CatsController {

 constructor(private readonly catsService: CatsService) {}
 @Post()

 async create(@Body() createCatDto: CreateCatDto) {

   this.catsService.create(createCatDto);

 }

 @Get()

 async findAll(): Promise<Cat[]> {

   return this.catsService.findAll();

 }

}

Consumiendo el API con Postman

Para acabar, vamos a hacer llamadas a este API desde Postman. Levantamos nuestra aplicación con el comando:

$ npm run start

Se levanta un servidor y se van logueando los endpoints que están disponibles y de qué tipo son:

Si nos fijamos en AppController, vemos que hay un endpoint GET disponible: se trata del “hola mundo” que se creó por defecto. En CatsController hay dos endpoints, uno de tipo GET y otro de tipo POST.

Empecemos por el GET de AppController. Si nos fijamos en la etiqueta decoradora, @Controller(), no recibe ningún parámetro, por lo que las peticiones se harán a la ruta raíz de nuestro servidor:

Ahora vamos a crear un objeto de tipo gato. Si vemos el decorador @Controller de Cats sí que recibe un parámetro cats, esta será la ruta a la que haremos las peticiones para este controlador:

Si ahora consultamos los gatos creados, veremos que efectivamente ha sido creado:

Instalación de Swagger

Como punto final a esta pequeña introducción a NestJS vamos a instalar su librería específica de Swagger y a configurarla.

$ npm install --save @nestjs/swagger swagger-ui-express

Una vez instalada, nos vamos al archivo main.ts y especificamos la configuración de Swagger (título, descripción, versión…).

Finalmente, se inyecta esta API Swagger en la aplicación y le damos una ruta de acceso, en este caso api.

import { NestFactory } from '@nestjs/core';

import { AppModule } from './app.module';

import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {

 const app = await NestFactory.create(AppModule);
 const options = new DocumentBuilder()

   .setTitle('Cats example')

   .setDescription('The cats API description')

   .setVersion('1.0')

   .addTag('cats')

   .build();

 const document = SwaggerModule.createDocument(app, options);

 SwaggerModule.setup('api', app, document);
 await app.listen(3000);

}

bootstrap();

Por último, añadimos una etiqueta decoradora básica @ApiProperty en el DTO CreateCatDto para que se muestre en Swagger sus propiedades bien definidas:

import { ApiProperty } from '@nestjs/swagger';

export class CreateCatDto {

 @ApiProperty()

 readonly name: string;

 @ApiProperty()

 readonly age: number;

 @ApiProperty()

 readonly breed: string;

}

Arrancamos el servidor y accedemos a la ruta /api:

Observamos los tres endpoints y si abrimos, por ejemplo, POST /cats, vemos la definición del objeto que el endpoint espera recibir al ser llamado:

Conclusión

En definitiva, es mi framework favorito a la hora de desarrollar backend porque ofrece un detalle que, a mi modo de ver, es diferencial: la posibilidad de construir backend en JavaScript de forma estructurada y con todo lo que te pueda hacer falta para un proyecto.

Cuéntanos qué te parece.

Enviar.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete