Configuración de Dockerfile para proyecto Nest.js: tutorial práctico

ronnymedina

Ronny Medina

Posted on June 22, 2024

Configuración de Dockerfile para proyecto Nest.js: tutorial práctico

Post original .

Hola a todos, en este post quiero hacer una actualización de mi post anterior en Nest.js. Quiero dejar una configuración básica para un proyecto nuevo con este framework usando Docker.

Requerimientos

  1. Tener Nodejs
  2. Tener Docker
  3. Conocimientos basicos en Docker

Instalación

El primer comando que necesitamos usar es el siguiente, que se encuentra en la página oficial, para poder instalar la CLI en nuestro sistema operativo y poder generar el proyecto.



$ npm i -g @nestjs/cli
$ nest new project-name


Enter fullscreen mode Exit fullscreen mode

Creando nuestro Dockerfile

Lo primero que necesitamos es crear un archivo que se encargue de levantar nuestra app en modo productivo o desarrollo. Este archivo se llamará run.sh



#!/bin/bash
echo "NODE_ENV: $NODE_ENV";

if [ "$NODE_ENV" == "prod" ]
    then
        echo "start prod mode...";
        npm run start:prod
else
    echo "start dev mode...";
    npm run start:dev
fi


Enter fullscreen mode Exit fullscreen mode

Este archivo lee la variable de entorno NODE_ENV y, en caso de ser el valor «prod», ejecutará el comando para correr en modo productivo; caso contrario, ejecutará nuestra app en modo desarrollo.

Luego de crear este archivo, vamos a crear nuestro archivo Dockerfile.



FROM node:20.14.0-bullseye

ENV APP_PORT 3000
ENV NODE_ENV prod
ENV WORKDIR_APP /var/prod

WORKDIR ${WORKDIR_APP}
COPY package.json .
RUN npm install
COPY . .

RUN npm run build

EXPOSE ${APP_PORT}

CMD ["bash", "run.sh"]


Enter fullscreen mode Exit fullscreen mode

Configurando nuestra app

En nuestro proyecto vamos a crear una carpeta llamada config y dentro un archivo llamado envs.ts. Este archivo tendrá nuestras variables de entorno de nuestra aplicación.



export const APP_PORT = parseInt(process.env.APP_PORT, 10);
export const APP_IS_PROD = process.env.NODE_ENV === 'prod'


Enter fullscreen mode Exit fullscreen mode

Luego vamos a configurar nuestro main.ts. Vamos a definir una variable llamada logLevel. Esta contendrá los niveles de log por defecto en modo productivo y agregaremos los otros en modo desarrollo. Esto es completamente configurable a sus necesidades.

Adicionalmente activaremos el versionamiento de nuestra API.




import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { APP_IS_PROD, APP_PORT } from './config/envs';
import { LogLevel, Logger, VersioningType } from '@nestjs/common';

async function bootstrap() {
  const logger = new Logger('bootstrap');
  const logLevel: LogLevel[] = ['error', 'warn', 'fatal', 'log'];

  if (!APP_IS_PROD) {
    logLevel.push('debug', 'verbose');  }


  const app = await NestFactory.create(AppModule, { logger: logLevel });

  app.enableVersioning({type: VersioningType.URI});

  await app.listen(APP_PORT);
}
bootstrap();



Enter fullscreen mode Exit fullscreen mode

Construir nuestra imagen

Ahora podemos correr los siguientes comandos para construir nuestra imagen.




$ docker build -t nestjs-setup .
$ docker run -it --rm -e NODE_ENV=develop -p "3000:3000" --name nest-setup nest-setup



Enter fullscreen mode Exit fullscreen mode

Ahora debemos poder acceder al puerto 3000 y ver el mensaje de Hello World!.

Agregando validaciones & Swagger

Adicionalmente voy a agregar los paquetes para las validaciones y para documentar nuestra API.

Para eso debemos entrar en nuestro contenedor que esta corriendo el siguiente comando:




$ docker exec -it nest-setup bash



Enter fullscreen mode Exit fullscreen mode

Agregar las siguientes dependencias:

  1. Swagger
  2. Class validator



$ yarn add class-validator class-transformer
$ yarn add @nestjs/swagger


Enter fullscreen mode Exit fullscreen mode

Para este punto, debemos actualizar nuestro package.json, ya que los cambios se realizaron dentro del contenedor y no estamos usando ningún volumen en este momento. Podemos copiar el archivo package.json desde nuestro contenedor usando docker cp. No es necesario que realice este paso, ya que al final del post tendrá todo el código con todas las dependencias.

Ahora en nuestro main vamos a agregar una configuración adicional para poder ver los errores de las validaciones cuando estemos en modo desarrollo. Este paso para ver los errores es opcional pero puede ser útil si desea aplicar otro caso de uso.




import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { APP_IS_PROD, APP_PORT } from './config/envs';
import { BadRequestException, LogLevel, Logger, ValidationError, ValidationPipe, VersioningType } from '@nestjs/common';

async function bootstrap() {
  const logger = new Logger('bootstrap');
  const logLevel: LogLevel[] = ['error', 'warn', 'fatal', 'log'];

  if (!APP_IS_PROD) {
    logLevel.push('debug', 'verbose');
  }

  const app = await NestFactory.create(AppModule, { logger: logLevel });

  app.enableVersioning({type: VersioningType.URI});

  const config = new DocumentBuilder()
    .setTitle('Cats example')
    .setDescription('The cats API description')
    .setVersion('1.0')
    .addTag('cats')
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('docs', app, document);

   // print validation errors
   app.useGlobalPipes(
    new ValidationPipe({
      enableDebugMessages: !APP_IS_PROD,
      transform: true,
      exceptionFactory: (errors: ValidationError[]) => {
        const allErrorMsg = errors.flatMap((error) =>
          Object.values(error.constraints),
        );

        if (!APP_IS_PROD) {
          logger.debug('Validations errors', allErrorMsg);
          logger.debug('Error schema', errors);
        }

        return new BadRequestException(allErrorMsg);
      },
    }),
  );


  await app.listen(APP_PORT);
}
bootstrap();




Enter fullscreen mode Exit fullscreen mode

Modificando nuestro app.controller.ts.




import { Body, Controller, Get, Post } from '@nestjs/common';
import { IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { AppService } from './app.service';

export class AppDto {
  @ApiProperty({ example: 'demo' })
  @IsString()
  name: string;
}

@Controller({
  version: '1',
  path: 'demo',
})
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Post()
  getHello(@Body() data: AppDto): string {
    return this.appService.getHello();
  }
}



Enter fullscreen mode Exit fullscreen mode

Ahora podemos acceder a la siguiente URL: http://localhost:3000/docs. Al enviar la solicitud, podemos ver que los datos se envían correctamente.

Si enviamos un número al momento de realizar la solicitud, podemos ver los mensajes de error también en la terminal.

Image description

Docker compose

Por ultimo podemos agregar nuestro docker-compose.yml



version: '3'
services:
nest-setup:
container_name: nest-setup
build: .
ebvironment:
- NODE_ENV=develop
ports:
- "3000:3000"
volumes:
- ./src:/var/prod/src
- ./test:/var/prod/test
- ./package.json:/var/prod/package.json

Enter fullscreen mode Exit fullscreen mode


$ docker compose up

Enter fullscreen mode Exit fullscreen mode




Recursos

💖 💪 🙅 🚩
ronnymedina
Ronny Medina

Posted on June 22, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related