====== Configuración de un proyecto ======
{{ configuracion-proyecto.png?450 }} Buenas prácticas en la configuración de proyectos
===== Convenciones de código / Guias de estilo ===== No existe ninguna guia de estilo oficial para desarrollar con lenguaje Javascript. Diferentes organizaciones o compañias elaboran las suyas propias para uso interno o bien para extender un buen estilo entre desarrolladores. Lo más importantes, al margen de escoger una u otra, es ser consistente con las convenciones de código o estilo que se decida seguir. Os dejo aqui un par de guias de estilo a modo de ejemplo: * [[https://www.mediawiki.org/wiki/Manual:Coding_conventions#JavaScript|MediaWiki Javascript Style Guide]] * [[https://google.github.io/styleguide/jsguide.html|Google Javascript Style Guide]] ===== Contenido generado ===== Son todos aquellos ficheros o carpetas que generan las herramientas que empleamos para desarrollar * Por parte del Sistema Operativo: * Sistemas MacOs: ''.DS_Store'' * Por parte del IDE: * IDEs de Jetbrains: ''.idea'' * Visual Studio Code: ''.vscode'' * Por parte del framework: * Node.js/npm: ''node_modules'' y ''package-lock.json'' * Sistemas de Control de Versiones: * Git: ''.git'' contiene toda la información necesaria del repositorio en el equipo local Representan todo lo que tendremos que añadir a nuestro fichero ''.gitignore'' para que no sea incluido en el repositorio del proyecto ===== Mantener estilos y convenciones entre diferentes IDEs: EditorConfig ===== https://editorconfig.org/ [*.js] indent_style = space indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true ===== Reinicio automático (hot reload) ===== [[https://www.npmjs.com/package/nodemon|nodemon]] es una herramienta que permite que nuestra aplicación nodejs se reinicie automáticamente cada vez que detecta un cambio en el código. De esa manera podemos evitar tener que estar continuamente parando y arrancándola. Primero tendremos que instalar ''nodemon'' de forma global (de forma que podamos ejecutarlo como un nuevo comando de nuestro equipo): santi@localhost:$ npm install -g nodemon A partir de entonces, si tenemos bien configurado el script ''start'' de nuestro proyecto en el fichero ''package.json'', podremos lanzar el proyecto simplemente ejecutando ''nodemon'' y éste monitorizará nuestro código en busca de cambios para reiniciarlo automáticamente. Asi podemos centrarnos simplemente en escribir código y comprobar si éste funciona correctamente: santi@localhost:$ nodemon [nodemon] 3.1.7 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,cjs,json [nodemon] starting `node src/app.js` Iniciando el backend en el puerto 8080 ===== Cómo gestionar el ciclo de vida de mi proyecto ===== https://en.wikipedia.org/wiki/Software_release_life_cycle ===== Qué version del framework elegir ===== A la hora de definir la configuración del proyecto, podemos también indicar para qué versiones del framework, node en este caso, está preparado para funcionar: . . . "name": "my-project", "version": "1.0.0", "engines": { "node": "20.x" . . . El calendario de releases de cualquier framework puede consultarse en su página web. En el caso de Nodejs, está disponible en https://nodejs.org/en/about/previous-releases
{{ node-releases.png }} Node Release Schedule (Fuente: https://nodejs.org/en/about/previous-releases)
Para saber qué cambios ha habido en una release/version concreta, tendremos que consultar lo que se conoce como //Changelog//. Por ejemplo, en https://nodejs.org/en/download/archive/v24.21.0 se pueden consultar el de Node v24 junto con otros detalles sobre esa release. ===== Cómo mantengo mi proyecto actualizado? ===== Otra parte importante, relacionada con el ciclo de vida del proyecto, es el cómo mantener el proyecto actualizado. Cualquier aplicación que hayamos desarrollado seguro que usará un número determinado de librerias (dependencias). Cada una de ellas es también a su vez un proyecto con su propio ciclo de vida: * Aparecerán nuevas releases de esas dependencias con nuevas funcionalidades (normalmente proporcionando //backward compatibility// (compatibilidad hacia atrás) * Algunas de esas releases simplemente corrigen fallos de seguridad (que pueden ser muy críticos) * Otras releases pueden contener lo que se conoce como //breaking changes//, lo que significa que algo podría dejar de funcionar o funcionar de forma inesperada si se actualiza a una versión concreta de esa dependencia (pero probablemente el ciclo de vida de esas librerias me acabará obligando a hacerlo en algún momento) * Una versión concreta de una dependencias, digamos 2.x.x, puede dejar de recibir cambios por llegar al final de su ciclo de vida. Podemos vernos obligados a pasar a la nueva versión, 3.x.x, con los cambios que ello podría suponer En el caso de proyectos Node.js, estos son algunos comandos que nos serán de utilidad a la hora de actualizar las dependencias de mi proyecto (algo que tendremos que hacer periódicamente): * ''npm audit'': Revisa todas las librerías utilizadas por el proyecto en busca de las vulnerabilidades asociadas a la versión especifica que se está usando * ''npm audit --fix'': Corrige las librerías que contengan vulnerabilidades que no contengan //breaking changes//, actualizando éstas a una nueva release. Actualiza el fichero ''package-lock.json'' y la carpeta ''node_modules''. Y actualizará el fichero ''package.json'' solamente cuando la librería que contenía la vulnerabilidad fuera una de las definidas en dicho fichero, y siempre según las reglas definidas en él. * ''npm outdated'': Muestra qué librerías están ya obsoletas comparándolas con las que está disponibles en el registro público de npm. No realiza ningún cambio ni actualización. * ''npm update'': Actualiza las dependencias del proyecto conforme a lo definido en el fichero ''package.json''. Modifica el fichero ''package-lock.json'' y la carpeta ''node_modules'' pero no toca el fichero ''package.json''. * ''npm update --save'': Actualiza las dependencias del proyecto conforme al comando anterior y actualiza también el fichero ''package.json'' con la versión mínimas de aquellas librerías que hayan sido actualizadas. A la hora de actualizar las dependencias será muy importante gestionar correctamente el [[https://docs.npmjs.com/about-semantic-versioning|versionado semántico]] de las librerias en el fichero ''package.json''. La información de todas las dependencias públicas disponibles para el framework Node.js se encuentran disponibles en el [[https://www.npmjs.com/|npm registry]], que contiene la información que el comando ''npm'' utiliza para realizar todas sus funciones. ===== Uso de linters ===== [[https://eslint.org/|eslint]] es una herramienta que funciona como linter. Un linter en una herramienta que realiza un análisis estático de código. Esto es, revisa el código en busca de errores de sintaxis o de estilo, potenciales errores por referencias a variables que no existen o variables que no se utilizan, . . . Para utilizar ''eslint'', tendremos que incluirlo como dependencia de desarrollo, ''devDependencies'', en nuestro ''package.json'', junto con una serie de plugins: { "name": "cities-backend", "version": "0.1", "description": "A sample project to learn Node.js", "type": "module", "scripts": { "lint": "eslint .", "lint:fix": "eslint . --fix", "start": "node src/app.js" }, "author": "Santiago Faci", "license": "GPL-2.0-only", "dependencies": { "express": "^4.21.1" }, "devDependencies": { "eslint": "6.4.0", "eslint-config-standard": "14.1.1", "eslint-plugin-import": "2.22.1", "eslint-plugin-node": "11.1.0", "eslint-plugin-promise": "4.3.1", "eslint-plugin-standard": "4.1.0" } } El siguiente paso será preparar un fichero de configuración para ''eslint''. En él definiremos las reglas que queremos que nuestro código siga, cuando queremos un error o un warning y cualquier otro parámetro. El siguiente es un ejemplo sencillo de un fichero de configuración que revisará aspectos como habituales como: - Que el indentado de código sea de 4 espacios - Que se emplee el punto y coma (semicolon) al final de cada sentencia - Que no haya puntos y coma adicionales - Nos avisará de las variables definidas que realmente no se usen - Nos permitirá usa sentencias como ''console.log'' para mostrar mensajes por pantalla (su uso no siempre está recomendado) module.exports = { "env": { "node": "true" }, "extends": [ "eslint:recommended" ], "parserOptions": { "ecmaVersion": 2020, "sourceType": "module" }, "rules": { "indent": ["error", 4], "semi": ["error", "always"], "no-extra-semi": "error", "no-unused-vars": "warn", "no-console": "off" } } ''npm runt lint'' ejecutará ''eslint'' para llevar a cabo el análisis estático de nuestro código, mostrándonos todos los errores encontrados: santi@zenbook:$ npm run lint > cities-backend@0.1 lint > eslint . /home/santi/Projects/cities-backend-module-linter/src/app.js 22:21 error Missing semicolon semi 26:1 error Expected indentation of 4 spaces but found 0 indent ✖ 2 problems (2 errors, 0 warnings) 2 errors and 0 warnings potentially fixable with the `--fix` option. Si queremos que ''eslint'' corrija todos los errores que pueda, podemos ejecutar ''npm run lint:fix'' que, en nuestro ''package.json'', lanza ''eslint'' con el flag ''--fix'' que arreglará todo los fallos posibles (en ocasiones hay problemas que tendremos que arreglar a mano) santi@zenbook:$ npm run lint:fix > cities-backend@0.1 lint > eslint . Es probable que no todo lo que forma parte del proyecto en un momento determinado deba ser analizado por ''eslint''. Hay mucho contenido generado que querremos ignorar. Para eso podemos indicarlo en el fichero ''.eslintignore'' y la herramienta ignorará esos ficheros o carpetas: coverage/ public/ submodules/ toolforge/ ===== Reutilización de código: exportar/importar funciones ===== ===== Documentación ===== Siempre que trabajemos en el desarrollo de un proyecto de software tendremos que desarrollar algún tipo de documentación. Esta documentación dependerá del tipo de usuario al que vaya destinado dicho proyecto: * Manual de usuario: Tendrás que hacer un manual de usuario cuando tu proyecto esté destinado para ser utilizado por usuarios humanos * Documentación técnica: En ocasiones estarás desarrollando librerías que otros desarrolladores necesitarán usar * Especificación API: Si desarrollas un backend con el objetivo de desarrollar una API REST, existe una especificación concreta que deberás de seguir, [[https://swagger.io/open-api/|Open API Specification]], también conocida como OAS Como los proyectos que estamos desarrollando son APIs, nos centraremos en la generación de documentación siguiendo la especificación Open API, concretamente para proyectos desarrollados con el framework Node.js ==== Enfoque API First ==== https://swagger.io/blog/understanding-the-api-first-approach-to-building-products/ ==== Enfoque Code First ==== Es la metodología tradicional. En este caso, primero se implementa la API en base a los requisitos funcionales y la documentación se generará más adelante (probablemente con alguna librería que permitirá que se integre de cierta manera con el código). En este enfoque se prioriza la velocidad de implementación. Cabe destacar que, en los últimos años, ha ido ganando protagonismo el otro enfoque, API First, por todas las ventajas que presenta. ===== Organización de un proyecto ===== En nuestro caso nos enfocaremos en la organización de un proyecto para el desarrollo de una API REST utilizando el framework de desarrollo de Node.js
{{ nodejs_api_structure.png }} Arquitectura API Node.js (Fuente: https://www.coreycleary.me/project-structure-for-an-express-rest-api-when-there-is-no-standard-way)
En nuestro caso trabajaremos con las siguientes capas: * **route**: Se definen las rutas o endpoints disponibles para cada uno de los elementos del modelo de datos * **controller**: Se definen las funciones que reciben las peticiones de las rutas definidas en la capa ''route'' * **service**: Funciones que definen la lógica de aplicación A continuación desarrollaremos una API completa (CRUD) utilizando una base de datos y con el código organizado correctamente siguiendo el modelo de capas que se ha planteado anteriormente. ===== Ficheros de configuración ===== Los ficheros de configuración se utilizan como vía para modificar el comportamiento de una aplicación sin tener que redesplegarla. Un cambio en el valor de una propiedad del fichero de configuración bastará para que la aplicación haga algo de forma diferente (siempre y cuando dicho comportamiento haya sido implementado en base a la propiedad correspondiente). Normalmente se utiliza un fichero con sintaxis ''yaml'' para almacenar la configuración: db: host: localhost port: 3306 user: user password: password database: cities service: port: 8080 Cómo cargar el fichero de configuración (la variable ''config'' acaba almacenando el contenido del fichero) const yaml = require('js-yaml'); const fs = require('fs'); const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); // Lee el fichero de configuración let configFile = 'config.prod.yaml'; const argv = yargs(hideBin(process.argv)).argv; if (argv.config != undefined) { configFile = argv.config; } const config = yaml.load(fs.readFileSync(configFile, 'utf-8')); module.exports = { config }; ===== Dockerizar un entorno de pruebas ===== https://www.docker.com/ https://www.docker.com/products/docker-desktop/ CREATE DATABASE cities; GRANT ALL PRIVILEGES ON cities.* TO user; USE cities; CREATE TABLE cities ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), population INT, altitude FLOAT, foundation_date DATE, age INT, area INT, density FLOAT, id_country INT ); INSERT INTO cities (name, population, altitude, foundation_date, age, area, density) VALUES ('Zaragoza', 700000, 200, '1000-01-01', 1000, 734734, 2373), ('Madrid', 5000000, 500, '1200-01-01', 800, 344734, 23473); name: cities services: db: image: mariadb:11.3.2 container_name: cities-dev-db environment: MARIADB_USER: user MARIADB_PASSWORD: password MARIADB_DATABASE: cities MARIADB_PORT: 3306 MARIADB_ROOT_PASSWORD: password ports: - "3306:3306" volumes: - ./db:/docker-entrypoint-initdb.d ===== Automatización con Makefile ===== https://dev.to/debajit13/make-makefiles-a-modern-developers-guide-to-classic-automation-14hl start-dev-env: docker compose -f docker-compose.dev.yaml up -d stop-dev-env: docker compose -f docker-compose.dev.yaml down all-test: stop-dev-env start-dev-env sleep 4 npm test .PHONY: all-test start-dev-env stop-dev-env santi@zenbook:$ make start-dev-env ===== Licencia del proyecto ===== https://en.wikipedia.org/wiki/Software_license ===== Mock API ===== { "$schema": "./node_modules/json-server/schema.json", "movies": [ { "id": "1", "title": "Spiderman", "views": 100 }, { "id": "2", "title": "Superman", "views": 200 }, { "id": "3", "title": "X-Men", "views": 550 }, { "id": "4", "title": "Batman", "views": 230 } ], "comments": [ { "id": "1", "text": "Que pelicula más buena", "movieId": "1" }, { "id": "2", "text": "Que pelicula más mala", "movieId": "2" }, { "id": "3", "text": "Que pelicula más normal", "movieId": "2" }, { "id": "4", "text": "Que pelicula más alucinante", "movieId": "3" } ] } santi@zenbook:$ npm install json-server@0.17.4 santi@zenbook:$ npx json-server db.json ---- ====== Ejercicios ====== - Configura un proyecto nodejs para que haga uso de ''eslint'' y se ejecute un análisis estático del código con 'npm run lint'. ''npm run lint:fix'' deberá lanzar el análisis y además corregir los fallos que sea posible. ---- (c) 2024-2026 Santiago Faci