By: Ronal Forero
todo lo referente a nest js, cada vez que se cree un controlador, lo va crear dentro de la carpeta src, sino le indicas otro directorio. Documentacion Oficial https://docs.nestjs.com/
Vea las nuevas noticias del Framework NestJs https://trilon.io/blog/
A) Lifecycle Event Use a Lifecycle Event (similar to change detection hooks in Angular) to run code and inject the services needed for it, e.g.:
Service
export class AppService implements OnModuleInit {
onModuleInit() {
console.log(`Initialization...`);
this.doStuff();
}
}
Module
export class ApplicationModule implements OnModuleInit {
constructor(private appService: AppService) {
}
onModuleInit() {
console.log(`Initialization...`);
this.appService.doStuff();
}
}
B) Execution Context Use the Execution Context to access any service in your main.ts:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
const appService = app.get(AppService);
}
Configurar Nestjs con Nodemailer
una de las cosas importantes es configurar el template para que siempre este disponible en la carpeta dist, para eso debemo editar el archivo nest-cli.json y agregar lo siguiente:
"compilerOptions":{ "assets": ["mail/templates/*"] } eso quiere decir que los templates estan en esa carpeta
Lo que esta pasando es que no se va guardar los template destro de la carpeta src, sino que guardaran afuera, asi hay que manejar esa ruta, para resolver ese problema hacermos uso del path.resolve. En el module el template se deberia configurar como
template: {
dir: path.resolve(__dirname, '../../mail/templates'),
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
},
- Obtener ayuda de Nest
- Crear un Nuevo Proyecto
- Configurar Debuger en VsCode
- Arrancar proyecto en modo de auto reinicio con algun cambio efectuado
- Crear un monorepo
- Generar todo con un solo comando
- Generar un Controlador
- Generar un modulo
- Generar un servicio
- Variables de Entorno
- Autenticacion
- Crear un Middleware
- Crear un Middleware con Express
- Crear un Middleware con Fastify
- Crear un Middleware con GraphQl
- Crear Cron Job
- Interfaces
- Schemas
- Data Object Transfer (DTO)
- Decoradores
- Mongoose paginate V2
- GraphQl
- Trabajando con Redis
- Informacion Adicional
- Como usar Axios
- Implementar loggin con winston
nest --help
Hace un tiempo vscode no craba la carpeta .vscode, pero ahora si lo hace. para el dia de hoy Mayo del 2020 esta tomando la configuracion del archivo launch.json. Vamos a crear un archivo dentro de la carpeta .vscode llamado launch.json y dentro del archivo vamos a pegar la siguiente configuracion
{
// Use IntelliSense para saber los atributos posibles.
// Mantenga el puntero para ver las descripciones de los existentes atributos.
// Para más información, visite: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Nest Framework",
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
"args": ["${workspaceFolder}/src/main.ts"],
"autoAttachChildProcesses": true
}
]
}
Para conocer como usar el debuger ver este video
https://www.youtube.com/watch?v=_3SeqNyPLeM
Podremos escoger el manejador de paquetes de nuestro proyecto, puede ser Yarm o Npm.
nest new nombre_del_proyecto
nest start --watch
Esta es la estructura basica, que uso para un controlador. Por ejemplo el entidad Subject (materia), tiene su servicio, modulo, schema, entre otros, tal como se muestra en la imagen
monorepo es una estrategia de desarrollo de software donde el código de muchos proyectos se almacena en el mismo repositorio.
nest new my-project
Esto va crear un proyecto normal, ahora entramos en la carpeta del proyecto y convertimos el proyecto en monorepo
cd my-project
vamos a convertir el proyecto en monorepo y ya luego podemos crear una aplicacion dentro del proyecto
nest g app otra-aplication
Como podemos crear una Gateway. La Gateway es la que podra Orquestar todos los micorservicios, para mas explicaion al respecto en la pagina oficial de Nest explican. La gateway es otro microservicio mas, es decir podremos ponerle cualquier nombre. Vamos a llamarle gateway
nest g app gateway
Ahora nos vamos al archivo nest-cli.json y vamos a colocar como root al gateway
"monorepo": true,
"root": "apps/gateway",
Al estar la gateway como root, podremos correla gateway sin escribir su nombre "gateway". Ejemplo
nest start --watch
Pero si por ejemplo tenemos el microservicio user entonces lo debemos correr asi
nest start user --watch
Generar controlador, servicio, dto y entitie con un solo comando. para rest, graphql...
instalar
npm i @nestjs/schematics --save
nets g resource nombre_del_compunente
Si queremos que nest te cree un CRUD del controlador, debes tener el siguiente paquete instalado
nest install @nestjs/schematics --save
ahora si podremos crear el controlador con el siguiente comando
nest g controller nombre_del_servicio
Un módulo es una clase anotada con un @Module() decorador. El @Module()decorador proporciona metadatos que Nest utiliza para organizar la estructura de la aplicación.
nest g mo nombre_del_modulo
Si vas a generar un servicio dentro de un microservicio, solo escriba el nombre de la carpeta/nombre_del_servicio luego nest te pide que inques a que microservico vas a generar el servicio.
nest g service nombre_del_servicio
Si deseamos crear un servicio y evitar que genere el archivo de pruebas, entonces debemos typear este comando y no el anterior.
nest g service nombre_del_servicio --no-spec
Debemos instalar el paquete que indican en la documentacion oficial, crear la carpeta config, crear el archivo .env, ya podremos usar el .env. Para base de datos TypeOrm hay que hacer un procedimiento...
Multiples Ambientes Para tener multiples Ambientes debemos hacer uso de NODE_ENV, esto una variable de entorno propia de NodeJS y del framework Express que se encuentra preseteada en tu aplicación. y tambien vamos a crear un archivo en el folder config
# env.config.ts name file
export const enviroments = {
dev: '.env',
test: '.test.env',
prod: '.prod.env',
};
En el archivo .env debe estar la variable NODE_ENV ejemplo
# .prod.env name file
NODE_ENV=test
# GENERAL
HOST=localhost
# DB
DB_HOST=
DB_PORT=
DB_NAME=
DB_USERNAME=
DB_PASSWORD=
DATABASE_URL=
Vamos a crear tres archivos .env porque solo vamos a usar 3 ambientes.. Importante debemos importar debtro de los imports el ConfigModule y que este al preincipio de todas las imprtaciones. Esto quiere decir que va buscar en el archivo que tenga el valor del NODE_ENV que le indiquen y que si no se encuentra entonces tome las variables del archiv .env, el valor de esta variable NODE_ENV se envia con el comando de ejecucion
# module principal de la app o del microservicio
ConfigModule.forRoot(
{envFilePath: enviroments[process.env.NODE_ENV] || '.env'},
),
El truco esta en correr la aplicacion, debemos usar otro comando, para que tome el .env dependiendo del ambiente, eso lo vamos a configurar en el package.json
# dentro del objecto "scrips" agregamos las siguientes lineas
"start:prod": "NODE_ENV=prod nest start",
"start:test": "NODE_ENV=test nest start",
"start:local": "NODE_ENV=local nest start",
Ejemplo: como correr el ambiente en un microservicio para ejecutra ambiente de desarrollo del microservicio base
NODE_ENV=test npm run start:dev base --watch
npm run start:test base
Errores: Existen muchos tutoriales para la Autenticacion y realmente Nestjs lo hace muy sencillo, recuerdo que en una oportunidad no me funcionaba la autenticcion, me salia no authorizado y el problema era que estaba encriptando con una clave y decencriptando con otra clave
https://docs.nestjs.com/middleware Las funciones de middleware pueden realizar las siguientes tareas:
- ejecutar cualquier código.
- realizar cambios en la solicitud y los objetos de respuesta.
- terminar el ciclo de solicitud-respuesta.
- llame a la siguiente función de middleware en la pila. si la función de middleware actual no finaliza el ciclo de solicitud-respuesta, debe llamar next()para pasar el control a la siguiente función de middleware. De lo contrario, la solicitud quedará pendiente.
Nota: un middlware no funciona en microservicios, ya que el moddleware funciona solo en http, y los microservicios usan trasnport que no son http
Podemos usar el middleware con una ruta con una ruta, tambien con una ruta y un metodo en especifico. Tambien podemos excluir rutas
Ejemplo vamos a crear un archivo logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('Request...');
next();
}
}
Ejemplo vamos a crear un archivo app.middleware.ts, en el caso de Fastify usa codigo vanila de JS
import { Injectable, NestMiddleware } from '@nestjs/common';
import { ServerResponse, IncomingMessage } from 'http';
@Injectable()
export class AppMiddleware implements NestMiddleware {
use(req: IncomingMessage, res: ServerResponse, next: Function) {
res.writeHead(200, { 'content-type': 'application/json' })
res.write(JSON.stringify({ test: "test" }))
res.end()
}
}
Este ejemplo funciona tanto en Express como en Fastify. El ejemplo imprime la request y la informacion solicitada con GraphQl. Aqui se esta usando fieldMiddleware, esto es ideal para hacer algun tipo de restriction. Por ejemplo la req se va imprimir tantas veces como campos se esten consultando. Lo que realmente hace es que va pasando field por field, si deseas poner una condicion entonces muy facil hacerlo.
export const checkRole: FieldMiddleware = async (
ctx: MiddlewareContext,
next: NextFn,
) => {
const { info } = ctx;
const { extensions } = info.parentType.getFields()[info.fieldName];
const value = await next();
const {context} = ctx;
const request = context.req
console.log("Request",request.user);
console.log("informacion que estas consultando",info);
return value;
};
Dentro del Module agregamos este codigo
imports: [
buildSchemaOptions: {
fieldMiddleware: [FieldMiddleware],
}
]
Crear un cron Job es sencillo en Nest. Pero quiero dejar documentado algo que me sucedio. Resulta que el cron job se ejecutaba y no paraba. Por mas que hacia cualquier cambio, eso no cambiaba el comportamiento del cron job. SOlucion: borrar la carpeta dist y volver a levantar el proyecto
Una Interface es un medio común para que los objetos no relacionados se comuniquen entre sí. Aqui unejemplo:
export interface Task {
id: string;
title: string;
description: TaskStatus;
}
export enum TaskStatus{
OPEN = "OPEN",
IN_PROGRESS = "IN_PROGRESS"
DONE = "DONE"
}
npm run build
Un decorador es el nombre de un patrón de diseño. Los decoradores alteran de manera dinámica la funcionalidad de una función, método o clase sin tener que hacer subclases o cambiar el código fuente de la clase decorada.
Este tipo de decoradores los podremos usar en los DTO para validar los campos.
@IsNotEmpty() Este decorador es para validar que el campo no puede estar en blanco, es decir no puede ser nulo.
@IsEmail() Decorador para validar que el campo es de tipo email.
@MinLength(6) Decorador para validar un minimo de caracteres, por ejemplo en este caso se limitara por 6 caracteres.
Este plugin por defecto devuelve 10 datos Se debe agregar en el esquema
const mongoosePaginate = require('mongoose-paginate-v2');
SubjectSchema.plugin(mongoosePaginate);
El paginate se usa para hacer un get, si por ejemplo queremos implementar el paginado a una tienda de productos
const products = await Product.paginate({name:laptop})
Lo que hicimos en la linea anterior es usar la funcion asincrona wait para hacer un get de productos, Product es el eschema del controlador y paginate llamar al plugin. Dentro de los coorchetes escribimos la query del GET. No es necesario escribir GET, ya que eso lo hace la funcion paginate
Si deseamos editar las propiedades del paginate y si no debemos buscar por ningun campo entonces dejamos los primeros {} como vacio. En los siguientes coorchetes pondremos las opciones que deseamos editar.
const products = await Product.paginate({},{limit:7})
https://www.npmjs.com/package/mongoose-paginate-v2
npm i --save redis
Instalamos dependencias
npm i cache-manager cache-manager-redis-store --save
Creamos un modulo para almacenar en cache
nest g module redis-cache
Creamos un servicio para el almacenamiento del cache
nest g service redis-cache
mas informacion: https://javascript.plainenglish.io/what-is-redis-and-how-to-use-it-with-nest-js-3cd1de0fe13b
REDIS_HOST : Especifica el host de nuestra base de datos de Redis (por ejemplo: localhost) REDIS_PORT : El valor del puerto predeterminado es 6479 CACHE_TTL : Especifica la cantidad de tiempo en segundos antes de que se invalide un valor MAX_ITEM_IN_CACHE : Especifica el número máximo de elementos que se deben mantener en la caché.
Esta esn una sesion con informacional adicional que todo programador debemos conocer y no esta de mas explicarlo.
GraphQL es un lenguaje de consulta y manipulación de datos para APIs, y un entorno de ejecución para realizar consultas con datos existentes. GraphQL fue desarrollado internamente por Facebook en 2012 antes de ser liberado públicamente en 2015.
https://blog.logrocket.com/code-first-vs-schema-first-development-graphql/
Axios es un Cliente HTTP basado en promesas para node. js y el navegador. Es isomorfico (= puede ejecutarse en el navegador y nodejs con el mismo código base). En el lado del servidor usa el modulo nativo http de node. A modo de ejemplo usaremos la siguiente url: https://hn.algolia.com/api/v1/search_by_date?query=nodejs
Otra cosa importante es que debemos importar el modulo de HttpModule, en el modulo donde vamos a trabajar con Axios
import { HttpService } from '@nestjs/axios';
const response= await lastValueFrom(this.httpService.get('https://hn.algolia.com/api/v1/search_by_date?query=nodejs'))
Debemos crear un archivo donde vamos agregar las interfaces (es opcional). Luego estando en archivo donde vamos agregar las interfaces, vamos a copiar el Json, luego tecleamos ctrl + p, visualcode mostrara un modal con un imput donde escribiremos el nombre de la extencion paste JSON as code, la extencion nos pedira el nombre de nivel superior, escribimos un nombre alucivo al JSON y ya. la extencion hara todo el trabajo.
Paste JSON as Code : Extension de quicktype, excelente extencion que nos ayuda a crear una interfaz de un JSON. Es decir crea tantas interfaces necearias como tipos de datos tenga el JSON.
Para usarla debemos tener el JSON en el porta papeles, osea debemos hacer copiado el json, luego hacemo control +p y buscamos la extencion y ya nos genera la interfaz
Windows Colors : Extension de Stuart Robinson, es ideal para cuando abres mas de un proyecto en Vscode y quieres distinguir entre una ventan que pertenece a un proyecto con otra ventana que pertenece a otro proyecto, esta extencion le pone un color en la franja del menu ixquierdo y eso nos ayudara a tener una referencia.
GitLens : Extension de GitKraken, es una extencion que esta vinculada con git, contiene muchas opciones, pero entre la mas resaltantes para mi. Es que si estas trabajando en un proyecto grupal, mostrara el usuario que hizo el commit en cada linea del codigo.
Prettier : Extension de Prettier, es una extencion que nos ayudara a formatear el codigo para que sea mas legible, debemos hacerle algunas configuraciones...
Better Comments : Extension de Aaron Bond. Hay una practia que he tomado y es que cuando me hace falta agregar cierta funcionalidad o en otras palabras tengo algo pendiente por hacer, hago un comentario y escribo "// TODO " asi en mayuscula. En otros IDE's como Android studio, este comentario lo resalta. bueno para hacer algo parecido en vscode... esta extencion nos ayudara ha resaltar todos los comentarios con la palabra TODO.
Excel Viewer : Extension de GrapeCity, es una extencion que nos ayudara a previsualizar archivos excell dentro vscode, algo parecido con los archivos smarkdownk, que vscode nos ayuda a previsualizar. Muy util para cuando trabajamos con Data y para no abrir un editor de Excell.
En español se traduce a interfaz, tecnicamente son un mecanismo de la programacion orientada a objetos (POO) que trata de suplir la carencia
de herencia múñtiple.
La diferencia de las clases que extiendes con respecto a las interfaces es que las interfaces no contienen implementación de sus métodos, por lo que la clase que implementa una interfaz debe escribir el código de todos los métodos que contiene. Por este motivo, se dice que las interfaces son como un contrato, en el que se especifica las cosas que debe contener una clase para que pueda implementar una interfaz o cumplir el contrato declarado por esa interfaz.
El patrón DTO (Data Transfer Object) tiene como finalidad la creación de objetos planos (POJO) con una serie de atributos que puedan ser enviados o recuperados del servidor en una sola invocación, de tal forma que un DTO puede contener información de múltiples fuentes o tablas y concentrarlas en una única clase simple.
Esto es ideal para solo enviar lo que se desea y no enviar datos de mas. Por ejemplo En la comunicacion entre microservicios, si deseamos enviar un objecto desde la gateway al microservicios user si en las dos partes el objecto esta definido como un tipo dto, entonces cada campo debe ser del tipo que se declaro en el DTO, esto tambien obliga a no enviar datos de otro tipo. Ejmplo si hay un campo que se declaro como string, debe ser string estrictamente.
las entidades son clases que representa al modelo de datos, o mapea directamente contra una tabla de la base de datos. Dicho esto, las entidades son clases que fueron diseñadas para mapear contra la base de datos, no para ser una vista para una pantalla o servicio determinado, lo que provoca que muchos de los campos no puedan ser serializables, no contengan todos los campos necesarios un servicio, ya sea que tengan de más o de menos.
<a name="typeorm_path"></a>
Para poder usar el path debemos tener disponibles las entidades en cada modulo. Si vamos a trabajar con relaciones que implican dos o mas entidades deben estar disponibles en el modulo. Si se estamos trabajando con el patron repository entonces deben estar los repository que se relacionan con cada entidad. Si hay una relacion usuario y transacion, entonces estas deben estar el forFeature disponibles.
imports: [ TypeOrmModule.forFeature([]) ],
entities: [__dirname + '/../**/*.entity{.ts,.js}']
hay ciertas librerias que se acoplan perfectamente a Nest, dejare aqui un lista
- Libreria papaparse : ideal para parsear archivos csv a json --> https://www.npmjs.com/package/nest-papaparse
- Tuve un probema en Microservicios, TyperORM, graphql. Implemente un guard de permission en la gateway, este guard se comunicaba con el microservicio useer, asi que tuve que agregar en el modulo una inyeccion para que eso estuviese disponible a cualquier cosa en la gateway.
providers: [
{
provide: 'USERS',
useFactory: ({ user }: ConfigService) => ClientProxyFactory.create(user),
inject: [ConfigService],
},
{
provide: APP_GUARD,
useClass: PermissionGuard,
},
ConfigService
]
El problema es que el guard se estaba ejecutando muchas veces y es que el arreglo de provide: APP_GUARD, no se debia poner en todos los modulos donde se debia inyectar, simplemente con ponerlo en un modulo ya se hace global. La solucion fue eliminar eso de todos los modulos.
Las tuberías tienen dos casos de uso típicos:
-
transformación : transforma los datos de entrada a la forma deseada (por ejemplo, de cadena a entero)
-
validación : evalúe los datos de entrada y, si son válidos, simplemente páselos sin cambios; de lo contrario, lanza una excepción cuando los datos son incorrectos
Existen los class validators, que nos ayudan a obligar que el dato sea el que deseamos, es decir que si llega otro tipo de dato se genere una exepcion. Podemos instalar la libreria de class validators, pero esta no va funcionar.
$ npm i --save class-validator class-transformer
Las tuberías globales se utilizan en toda la aplicación, para cada controlador y cada controlador de ruta.
EN microservicios se usa el @Payload y si usamos class validator para los DTO, es importante aseguranos que la data que llega al controlador (en el caso de que se use una Gateway o sino al servicio) sea de tipo DTO y que el DTO contenga los decoradores. Para implementar los pipes de forma global, vamos al main del microservicio y agregamos el siguiente codigo. Cabe destacar que es de esta forma porque la respuesta lega al gateway donde esta implementado graphql
app.useGlobalPipes(
new ValidationPipe({
transform: true,
transformOptions:{
enableImplicitConversion: true
},
exceptionFactory: (errors) => {
const errorMessages = {};
errors.forEach(error => {
errorMessages[error.property]= Object.values(error.constraints).join('. ').trim();
})
const error = Object.values(errorMessages)[0];
return new RpcException(error.toString());
}
})
)
Excelente tutorial:
https://ualmtorres.github.io/SeminarioTesting/
Para ejecutar unicamente los test typeamos el siguiente comando. Pero este comando ejecutara todos los test de una vez
npm run test:watch
Ejecutar un test en especifico. Por ejemplo el test del controlador de componente User
npm run test -f user.controller.spec.ts :watch
Wiston nos permite guardar los logs en archivos, esto es mu importante para una aplicacion. El poder hacer seguimiento de las cosas que pasan desde un log, Los logs son como otra base de datos, donde podemos ver que es lo que esta pasando en el produccion. https://github.com/gremo/nest-winston
TRACE – log events with this level are the most fine-grained and are usually not needed unless you need to have the full visibility of what is happening in your application and inside the third-party libraries that you use. You can expect the TRACE logging level to be very verbose.
DEBUG – less granular compared to the TRACE level, but still more than you will need in everyday use. The DEBUG log level should be used for information that may be needed for deeper diagnostics and troubleshooting.
INFO – the standard log level indicating that something happened, application processed a request, etc. The information logged using the INFO log level should be purely informative and not looking into them on a regular basis shouldn’t result in missing any important information.
WARN – the log level that indicates that something unexpected happened in the application. For example a problem, or a situation that might disturb one of the processes, but the whole application is still working.
ERROR – the log level that should be used when the application hits an issue preventing one or more functionalities from properly functioning. The ERROR log level can be used when one of the payment systems is not available, but there is still the option to check out the basket in the e-commerce application or when your social media logging option is not working for some reason. You can also see the ERROR log level associated with exceptions.
Documentacion de Nest JS https://docs.nestjs.com/faq
Blog de Brando Juberd https://blog.devgenius.io/deprecation-warning-with-graphql-nestjs-versionless-api-badcba08cb1f