Saltar al contenido

d

Validación y ESLint

Por lo general, existen restricciones que queremos aplicar a los datos que se almacenan en la base de datos de nuestra aplicación. Nuestra aplicación no debe aceptar notas que tengan una propiedad content vacía o faltante. La validez de la nota se comprueba en el controlador de ruta:

app.post('/api/notes', (request, response) => {
  const body = request.body
  if (body.content === undefined) {    return response.status(400).json({ error: 'content missing' })  }
  // ...
})

Si la nota no tiene la propiedad content, respondemos a la solicitud con el código de estado 400 bad request.

Una forma más inteligente de validar el formato de los datos antes de que se almacenen en la base de datos es utilizar la funcionalidad de validación disponible en Mongoose.

Podemos definir reglas de validación específicas para cada campo en el esquema:

const noteSchema = new mongoose.Schema({
  content: {    type: String,    minLength: 5,    required: true  },  important: Boolean
})

El campo content ahora requiere tener al menos cinco caracteres de longitud y esta definido como required, lo que significa que no puede faltar. No hemos agregado ninguna restricción al campo important, por lo que su definición en el esquema no ha cambiado.

Los validadores minlength y required están integrados y proporcionados por Mongoose. La funcionalidad del validador personalizado de Mongoose nos permite crear nuevos validadores, si ninguno de los integrados cubre nuestras necesidades.

Si intentamos almacenar un objeto en la base de datos que rompe una de las restricciones, la operación lanzará una excepción. Cambiemos nuestro controlador para crear una nueva nota para que pase las posibles excepciones al middleware del controlador de errores:

app.post('/api/notes', (request, response, next) => {  const body = request.body

  const note = new Note({
    content: body.content,
    important: body.important || false,
  })

  note.save()
    .then(savedNote => {
      response.json(savedNote)
    })
    .catch(error => next(error))})

Expandamos el controlador de errores para tratar estos errores de validación:

const errorHandler = (error, request, response, next) => {
  console.error(error.message)

  if (error.name === 'CastError') {
    return response.status(400).send({ error: 'malformatted id' })
  } else if (error.name === 'ValidationError') {    return response.status(400).json({ error: error.message })  }

  next(error)
}

Cuando falla la validación de un objeto, devolvemos el siguiente mensaje de error predeterminado de Mongoose:

postman mostrando mensaje de error

Notamos que el backend ahora tiene un problema: las validaciones no se realizan al editar una nota. La documentación aborda el problema explicando que las validaciones no se ejecutan por defecto cuando se utilizan los métodos findOneAndUpdate y métodos relacionados.

La solución es sencilla. También reformulemos un poco el código de la ruta:

app.put('/api/notes/:id', (request, response, next) => {
  const { content, important } = request.body
  Note.findByIdAndUpdate(
    request.params.id, 
    { content, important },    { new: true, runValidators: true, context: 'query' }  ) 
    .then(updatedNote => {
      response.json(updatedNote)
    })
    .catch(error => next(error))
})

Desplegando el backend con base de datos a producción

La aplicación debería funcionar casi tal como está en Fly.io/Render. No necesitamos generar una nueva versión de producción del frontend, ya que los cambios realizados hasta ahora solo afectan a nuestro backend.

Las variables de entorno definidas en dotenv solo se usarán cuando el backend no esté en modo de producción, es decir, en Fly.io o Render.

Para producción, debemos establecer la URL de la base de datos en el servicio que está alojando nuestra aplicación.

En Fly.io se hace con fly secrets set:

fly secrets set MONGODB_URI='mongodb+srv://fullstack:thepasswordishere@cluster0.o1opl.mongodb.net/noteApp?retryWrites=true&w=majority'

Cuando la aplicación está en desarrollo, es muy probable que algo falle. Por ejemplo, cuando desplegué mi aplicación por primera vez con la base de datos, no se veía ni una sola nota:

navegador sin notas

La pestaña de red de la consola del navegador reveló que la obtención de las notas no tuvo éxito; la solicitud permaneció en estado pendiente durante mucho tiempo hasta que falló con un código de estado 502.

¡La consola del navegador debe estar abierta todo el tiempo!

También es vital seguir continuamente los registros del servidor. El problema se hizo evidente cuando se abrieron los registros con fly logs:

registro del servidor fly.io mostrando conexión a undefined

La URL de la base de datos era undefined, por lo que el comando fly secrets set MONGODB_URI no fue utilizado.

También necesitarás agregar a la whitelist (lista blanca) la dirección IP de la aplicación de fly.io en MongoDB Atlas. Si no lo haces, MongoDB rechazará la conexión.

Lamentablemente, fly.io no te proporciona una dirección IPv4 dedicada para tu aplicación, por lo que necesitarás permitir todas las direcciones IP en MongoDB Atlas.

Cuando se utiliza Render, la URL de la base de datos se proporciona definiendo la variable de entorno adecuada en el panel de control:

Render Dashboard mostrando la variable de entorno MONGODB_URI

El panel de control de Render muestra los registros del servidor:

Render Dashboard con flecha apuntando al servidor en ejecución en el puerto 10000

Puedes encontrar el código de nuestra aplicación actual en su totalidad en la rama part3-6 de este repositorio de GitHub.

Lint

Antes de pasar a la siguiente parte, veremos una herramienta importante llamada lint. Wikipedia dice lo siguiente sobre lint:

Genéricamente, lint o linter es cualquier herramienta que detecta y marca errores en los lenguajes de programación, incluidos los errores de estilo. El término comportamiento lint-like a veces se aplica al proceso de marcar el uso de lenguaje sospechoso. Las herramientas de tipo lint generalmente realizan análisis estáticos del código fuente.

En lenguajes compilados de tipado estático como Java, los IDE como NetBeans pueden señalar errores en el código, incluso aquellos que son más que simples errores de compilación. Se pueden utilizar herramientas adicionales para realizar análisis estáticos, como checkstyle, para ampliar las capacidades del IDE y señalar también problemas relacionados con el estilo, como la indentación.

En el universo de JavaScript, la herramienta líder actual para el análisis estático (también conocida como "linting") es ESlint.

Instalemos ESlint como una dependencia de desarrollo del proyecto de backend con el comando:

npm install eslint --save-dev

Después de esto, podemos inicializar una configuración predeterminada de ESlint con el comando:

npx eslint --init

Responderemos todas las preguntas:

salida del terminal de ESlint init

La configuración se guardará en el archivo .eslintrc.js. Cambiaremos browser a node en la configuración de env:

module.exports = {
    "env": {
        "commonjs": true,
        "es2021": true,
        "node": true    },
    "overrides": [
        {
            "env": {
                "node": true
            },
            "files": [
                ".eslintrc.{js,cjs}"
            ],
            "parserOptions": {
                "sourceType": "script"
            }
        }
    ],
    "parserOptions": {
        "ecmaVersion": "latest"
    },
    "rules": {
    }
}

Cambiemos un poco la configuración. Instala un plugin que define un conjunto de reglas relacionadas al estilo:

npm install --save-dev @stylistic/eslint-plugin-js

Habilita el plugin y agrega una definición de "extends" y cuatro reglas de estilo:

module.exports = {
    // ...
    'plugins': [
        '@stylistic/js'
    ],
    'extends': 'eslint:recommended',
    'rules': {
        '@stylistic/js/indent': [
            'error',
            2
        ],
        '@stylistic/js/linebreak-style': [
            'error',
            'unix'
        ],
        '@stylistic/js/quotes': [
            'error',
            'single'
        ],
        '@stylistic/js/semi': [
            'error',
            'never'
        ],
    }
}

Extends eslint:recommended añade un conjunto de reglas recomendadas al proyecto. Además, se han añadido reglas para la indentación, saltos de línea, guiones y puntos y comas. Estas cuatro reglas están todas definidas en el plugin de estilos de Eslint.

Inspeccionar y validar un archivo como index.js se puede hacer con el siguiente comando:

npx eslint index.js

Es recomendable crear un script npm separado para linting:

{
  // ...
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    // ...
    "lint": "eslint ."  },
  // ...
}

Ahora, el comando npm run lint comprobará todos los archivos del proyecto.

Además, los archivos del directorio dist se comprueban cuando se ejecuta el comando. No queremos que esto suceda, y podemos lograrlo creando un archivo .eslintignore en la raíz del proyecto con el siguiente contenido:

dist

Esto hace que el directorio dist no sea comprobado por ESlint.

Lint tiene mucho que decir sobre nuestro código:

salida de consola con errores de ESlint

No solucionemos estos problemas todavía.

Una mejor alternativa a ejecutar el linter desde la línea de comandos es configurar un eslint-plugin en el editor, que ejecuta el linter continuamente. Al usar el plugin, verá errores en su código de inmediato. Puede encontrar más información sobre el plugin Visual Studio ESLint aquí.

El plugin de VS Code ESlint subrayará las violaciones de estilo con una línea roja:

plugin de ESlint mostrando errores en el código

Esto hace que los errores sean fáciles de detectar y puedan ser corregidos de inmediato.

ESlint tiene una amplia gama de reglas que son fáciles de usar al editar el archivo .eslintrc.js.

Agreguemos la regla eqeqeq que nos alerta si la igualdad se verifica con algo que no sea el operador de triple igual. La regla se agrega bajo el campo rules en el archivo de configuración.

{
  // ...
  'rules': {
    // ...
   'eqeqeq': 'error',
  },
}

Ya que estamos en eso, hagamos algunos otros cambios en las reglas.

Evitemos los espacios finales innecesarios al final de las líneas, exijamos que siempre haya un espacio antes y después de las llaves, y exijamos también un uso consistente de espacios en blanco en los parámetros de función de las funciones de flecha.

{
  // ...
  'rules': {
    // ...
    'eqeqeq': 'error',
    'no-trailing-spaces': 'error',
    'object-curly-spacing': [
        'error', 'always'
    ],
    'arrow-spacing': [
        'error', { 'before': true, 'after': true }
    ]
  },
}

Nuestra configuración predeterminada utiliza un montón de reglas predeterminadas de eslint:recommended:

'extends': 'eslint:recommended',

Esto incluye una regla que advierte sobre los comandos console.log. La desactivación de una regla se puede lograr definiendo su "valor" como 0 en el archivo de configuración. Mientras tanto, hagamos esto para la regla no-console.

{
  // ...
  'rules': {
    // ...
    'eqeqeq': 'error',
    'no-trailing-spaces': 'error',
    'object-curly-spacing': [
        'error', 'always'
    ],
    'arrow-spacing': [
        'error', { 'before': true, 'after': true }
    ],
    'no-console': 0  },
}

NB cuando realizas cambios en el archivo .eslintrc.js, se recomienda ejecutar el linter desde la línea de comandos. Esto verificará que el archivo de configuración esté formateado correctamente:

salida de terminal del comando npm run lint

Si hay algún problema en tu archivo de configuración, el plugin lint puede comportarse de manera bastante errática.

Muchas empresas definen estándares de codificación que se aplican en toda la organización a través del archivo de configuración de ESlint. No se recomienda seguir reinventando la rueda una y otra vez, y puede ser una buena idea adoptar una configuración ya hecha del proyecto de otra persona en el tuyo. Recientemente, muchos proyectos han adoptado la guía de estilo Javascript de Airbnb al utilizar la configuración ESlint de Airbnb.

Puedes encontrar el código para nuestra aplicación actual en su totalidad en la rama part3-7 de este repositorio de GitHub.