Cómo configurar un proyecto back-end de Express API con PostgreSQL
Publicado: 2022-03-10Adoptaremos un enfoque de desarrollo basado en pruebas (TDD) y configuraremos el trabajo de integración continua (CI) para ejecutar automáticamente nuestras pruebas en Travis CI y AppVeyor, completo con informes de cobertura y calidad del código. Aprenderemos sobre controladores, modelos (con PostgreSQL), manejo de errores y middleware Express asíncrono. Finalmente, completaremos la canalización de CI/CD configurando la implementación automática en Heroku.
Parece mucho, pero este tutorial está dirigido a principiantes que están listos para probar un proyecto de back-end con cierto nivel de complejidad, y que aún pueden estar confundidos en cuanto a cómo encajan todas las piezas en un proyecto real. .
Es robusto sin ser abrumador y se divide en secciones que puede completar en un período de tiempo razonable.
Empezando
El primer paso es crear un nuevo directorio para el proyecto y comenzar un nuevo proyecto de nodo. Se requiere Node para continuar con este tutorial. Si no lo tiene instalado, diríjase al sitio web oficial, descárguelo e instálelo antes de continuar.
Usaré yarn como mi administrador de paquetes para este proyecto. Hay instrucciones de instalación para su sistema operativo específico aquí. Siéntase libre de usar npm si lo desea.
Abra su terminal, cree un nuevo directorio e inicie un proyecto Node.js.
# create a new directory mkdir express-api-template # change to the newly-created directory cd express-api-template # initialize a new Node.js project npm init
Responda las preguntas que siguen para generar un archivo package.json . Este archivo contiene información sobre su proyecto. El ejemplo de dicha información incluye qué dependencias usa, el comando para iniciar el proyecto, etc.
Ahora puede abrir la carpeta del proyecto en el editor de su elección. Yo uso código de estudio visual. Es un IDE gratuito con toneladas de complementos para facilitarle la vida y está disponible para todas las plataformas principales. Puedes descargarlo desde el sitio web oficial.
Cree los siguientes archivos en la carpeta del proyecto:
- LÉAME.md
- .editorconfig
Aquí hay una descripción de lo que hace .editorconfig desde el sitio web EditorConfig. (Probablemente no lo necesites si trabajas solo, pero no hace daño, así que lo dejaré aquí).
"EditorConfig ayuda a mantener estilos de codificación coherentes para varios desarrolladores que trabajan en el mismo proyecto en varios editores e IDE".
Abra .editorconfig
y pegue el siguiente código:
root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = false insert_final_newline = true
El [*]
significa que queremos aplicar las reglas que se encuentran debajo de él a cada archivo del proyecto. Queremos un tamaño de sangría de dos espacios y un conjunto de caracteres UTF-8
. También queremos recortar el espacio en blanco final e insertar una última línea vacía en nuestro archivo.
Abra README.md y agregue el nombre del proyecto como un elemento de primer nivel.
# Express API template
Agreguemos el control de versiones de inmediato.
# initialize the project folder as a git repository git init
Cree un archivo .gitignore e ingrese las siguientes líneas:
node_modules/ yarn-error.log .env .nyc_output coverage build/
Estos son todos los archivos y carpetas que no queremos rastrear. Todavía no los tenemos en nuestro proyecto, pero los veremos a medida que avancemos.
En este punto, debería tener la siguiente estructura de carpetas.
EXPRESS-API-TEMPLATE ├── .editorconfig ├── .gitignore ├── package.json └── README.md
Considero que este es un buen punto para confirmar mis cambios y enviarlos a GitHub.
Inicio de un nuevo proyecto Express
Express es un marco Node.js para crear aplicaciones web. Según el sitio web oficial, es un
Framework web minimalista, rápido y sin opiniones para Node.js.
Hay otros marcos de aplicaciones web excelentes para Node.js, pero Express es muy popular, con más de 47 000 estrellas de GitHub en el momento de escribir este artículo.
En este artículo, no vamos a tener muchas discusiones sobre todas las partes que componen Express. Para esa discusión, le recomiendo que consulte la serie de Jamie. La primera parte está aquí, y la segunda parte está aquí.
Instale Express y comience un nuevo proyecto Express. Es posible configurar manualmente un servidor Express desde cero, pero para hacernos la vida más fácil, usaremos el generador express para configurar el esqueleto de la aplicación.
# install the express generator globally yarn global add express-generator # install express yarn add express # generate the express project in the current folder express -f
El indicador -f
obliga a Express a crear el proyecto en el directorio actual.
Ahora realizaremos algunas operaciones de limpieza de la casa.
- Elimine el archivo index/users.js .
- Elimine las carpetas
public/
yviews/
. - Cambie el nombre del archivo bin/www a bin/www.js .
- Desinstale
jade
con el comandoyarn remove jade
. - Cree una nueva carpeta llamada
src/
y mueva lo siguiente dentro de ella: 1. archivo app.js 2. carpetabin/
3. carpetaroutes/
dentro. - Abra package.json y actualice el script de
start
para que se vea como se muestra a continuación.
"start": "node ./src/bin/www"
En este punto, la estructura de carpetas de su proyecto se ve a continuación. Puede ver cómo VS Code resalta los cambios de archivo que se han producido.
EXPRESS-API-TEMPLATE ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .editorconfig ├── .gitignore ├── package.json ├── README.md └── yarn.lock
Abra src/app.js y reemplace el contenido con el siguiente código.
var logger = require('morgan'); var express = require('express'); var cookieParser = require('cookie-parser'); var indexRouter = require('./routes/index'); var app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use('/v1', indexRouter); module.exports = app;
Después de requerir algunas bibliotecas, le indicamos a Express que maneje cada solicitud que llegue a /v1
con indexRouter
.
Reemplace el contenido de route/index.js con el siguiente código:
var express = require('express'); var router = express.Router(); router.get('/', function(req, res, next) { return res.status(200).json({ message: 'Welcome to Express API template' }); }); module.exports = router;
Tomamos Express, creamos un enrutador a partir de él y servimos la ruta /
, que devuelve un código de estado de 200
y un mensaje JSON.
Inicie la aplicación con el siguiente comando:
# start the app yarn start
Si configuró todo correctamente, solo debería ver $ node ./src/bin/www
en su terminal.
Visite https://localhost:3000/v1
en su navegador. Deberías ver el siguiente mensaje:
{ "message": "Welcome to Express API template" }
Este es un buen punto para confirmar nuestros cambios.
- La rama correspondiente en mi repositorio es 01-install-express.
Convirtiendo nuestro código a ES6
El código generado por express-generator
está en ES5
, pero en este artículo escribiremos todo nuestro código en la sintaxis de ES6
. Entonces, convirtamos nuestro código existente a ES6
.
Reemplace el contenido de route/index.js con el siguiente código:
import express from 'express'; const indexRouter = express.Router(); indexRouter.get('/', (req, res) => res.status(200).json({ message: 'Welcome to Express API template' }) ); export default indexRouter;
Es el mismo código que vimos anteriormente, pero con la declaración de importación y una función de flecha en el controlador de ruta /
.
Reemplace el contenido de src/app.js con el siguiente código:
import logger from 'morgan'; import express from 'express'; import cookieParser from 'cookie-parser'; import indexRouter from './routes/index'; const app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use('/v1', indexRouter); export default app;
Ahora echemos un vistazo al contenido de src/bin/www.js . Lo construiremos de forma incremental. Elimine el contenido de src/bin/www.js
y péguelo en el siguiente bloque de código.
#!/usr/bin/env node /** * Module dependencies. */ import debug from 'debug'; import http from 'http'; import app from '../app'; /** * Normalize a port into a number, string, or false. */ const normalizePort = val => { const port = parseInt(val, 10); if (Number.isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; }; /** * Get port from environment and store in Express. */ const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * Create HTTP server. */ const server = http.createServer(app); // next code block goes here
Este código comprueba si se especifica un puerto personalizado en las variables de entorno. Si no se establece ninguno, el valor de puerto predeterminado de 3000
se establece en la instancia de la aplicación, después de que normalizePort lo normalizePort
a una cadena o un número. Luego, el servidor se crea a partir del módulo http
, con la app
como función de devolución de llamada.
La línea de #!/usr/bin/env node
es opcional ya que especificaríamos el nodo cuando queramos ejecutar este archivo. Pero asegúrese de que esté en la línea 1 del archivo src/bin/www.js o elimínelo por completo.
Echemos un vistazo a la función de manejo de errores. Copie y pegue este bloque de código después de la línea donde se crea el servidor.
/** * Event listener for HTTP server "error" event. */ const onError = error => { if (error.syscall !== 'listen') { throw error; } const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': alert(`${bind} requires elevated privileges`); process.exit(1); break; case 'EADDRINUSE': alert(`${bind} is already in use`); process.exit(1); break; default: throw error; } }; /** * Event listener for HTTP server "listening" event. */ const onListening = () => { const addr = server.address(); const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`; debug(`Listening on ${bind}`); }; /** * Listen on provided port, on all network interfaces. */ server.listen(port); server.on('error', onError); server.on('listening', onListening);
La función onError
errores en el servidor http y muestra los mensajes de error correspondientes. La función onListening
simplemente envía el puerto que el servidor está escuchando a la consola. Finalmente, el servidor escucha las solicitudes entrantes en la dirección y el puerto especificados.
En este punto, todo nuestro código existente está en sintaxis ES6
. Detenga su servidor (use Ctrl + C ) y ejecute yarn start
. Obtendrá un error SyntaxError: Invalid or unexpected token
. Esto sucede porque Node (en el momento de escribir esto) no admite parte de la sintaxis que hemos usado en nuestro código.
Ahora lo arreglaremos en la siguiente sección.
Configuración de dependencias de desarrollo: babel
, nodemon
, eslint
y prettier
Es hora de configurar la mayoría de los scripts que vamos a necesitar en esta fase del proyecto.
Instale las bibliotecas requeridas con los siguientes comandos. Puedes simplemente copiar todo y pegarlo en tu terminal. Las líneas de comentarios se omitirán.
# install babel scripts yarn add @babel/cli @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/register @babel/runtime @babel/node --dev
Esto instala todos los scripts de babel enumerados como dependencias de desarrollo. Verifique su archivo package.json y debería ver una sección devDependencies
. Todos los scripts instalados se enumerarán allí.
Los scripts de babel que estamos usando se explican a continuación:
@babel/cli | Una instalación requerida para usar babel . Permite el uso de Babel desde la terminal y está disponible como ./node_modules/.bin/babel . |
@babel/core | Funcionalidad básica de Babel. Esta es una instalación requerida. |
@babel/node | Esto funciona exactamente como la CLI de Node.js, con el beneficio adicional de compilar con preajustes y complementos de babel . Esto es necesario para su uso con nodemon . |
@babel/plugin-transform-runtime | Esto ayuda a evitar la duplicación en la salida compilada. |
@babel/preset-env | Una colección de complementos que se encargan de realizar transformaciones de código. |
@babel/register | Esto compila archivos sobre la marcha y se especifica como un requisito durante las pruebas. |
@babel/runtime | Esto funciona junto con @babel/plugin-transform-runtime . |
Cree un archivo llamado .babelrc en la raíz de su proyecto y agregue el siguiente código:
{ "presets": ["@babel/preset-env"], "plugins": ["@babel/transform-runtime"] }
Instalamos nodemon
# install nodemon yarn add nodemon --dev
nodemon
es una biblioteca que monitorea el código fuente de nuestro proyecto y reinicia automáticamente nuestro servidor cada vez que observa algún cambio.
Cree un archivo llamado nodemon.json en la raíz de su proyecto y agregue el código a continuación:
{ "watch": [ "package.json", "nodemon.json", ".eslintrc.json", ".babelrc", ".prettierrc", "src/" ], "verbose": true, "ignore": ["*.test.js", "*.spec.js"] }
La tecla de watch
le dice a nodemon
qué archivos y carpetas debe vigilar los cambios. Entonces, cada vez que alguno de estos archivos cambia, nodemon reinicia el servidor. La tecla ignore
le dice a los archivos que no busquen cambios.
Ahora actualice la sección de secuencias de scripts
de su archivo package.json para que tenga el siguiente aspecto:
# build the content of the src folder "prestart": "babel ./src --out-dir build" # start server from the build folder "start": "node ./build/bin/www" # start server in development mode "startdev": "nodemon --exec babel-node ./src/bin/www"
- Los scripts de
prestart
crean el contenido de la carpetasrc/
y lo colocan en la carpetabuild/
. Cuando emite el comando deyarn start
, este script se ejecuta primero antes que el script destart
. -
start
script ahora sirve el contenido de la carpetabuild/
en lugar de la carpetasrc/
que estábamos sirviendo anteriormente. Este es el script que usará cuando entregue el archivo en producción. De hecho, servicios como Heroku ejecutan automáticamente este script cuando realiza la implementación. -
yarn startdev
se utiliza para iniciar el servidor durante el desarrollo. De ahora en adelante usaremos este script a medida que desarrollemos la aplicación. Tenga en cuenta que ahora estamos usandobabel-node
para ejecutar la aplicación en lugar delnode
normal. El indicador--exec
obligababel-node
a servir la carpetasrc/
. Para el script destart
, usamosnode
ya que los archivos en la carpetabuild/
se compilaron en ES5.
Ejecute yarn startdev
y visite https://localhost:3000/v1. Su servidor debería estar en funcionamiento nuevamente.
El último paso en esta sección es configurar ESLint
y prettier
. ESLint ayuda a hacer cumplir las reglas de sintaxis, mientras que Prettier ayuda a formatear nuestro código correctamente para facilitar la lectura.
Agregue ambos con el siguiente comando. Debe ejecutar esto en una terminal separada mientras observa la terminal donde se ejecuta nuestro servidor. Debería ver el reinicio del servidor. Esto se debe a que estamos monitoreando el archivo package.json en busca de cambios.
# install elsint and prettier yarn add eslint eslint-config-airbnb-base eslint-plugin-import prettier --dev
Ahora cree el archivo .eslintrc.json en la root
del proyecto y agregue el siguiente código:
{ "env": { "browser": true, "es6": true, "node": true, "mocha": true }, "extends": ["airbnb-base"], "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" }, "rules": { "indent": ["warn", 2], "linebreak-style": ["error", "unix"], "quotes": ["error", "single"], "semi": ["error", "always"], "no-console": 1, "comma-dangle": [0], "arrow-parens": [0], "object-curly-spacing": ["warn", "always"], "array-bracket-spacing": ["warn", "always"], "import/prefer-default-export": [0] } }
Este archivo define principalmente algunas reglas contra las cuales eslint
verificará nuestro código. Puede ver que estamos ampliando las reglas de estilo utilizadas por Airbnb.
En la sección "rules"
, definimos si eslint
debe mostrar una advertencia o un error cuando encuentra ciertas violaciones. Por ejemplo, muestra un mensaje de advertencia en nuestro terminal por cualquier sangría que no use 2 espacios. Un valor de [0]
desactiva una regla, lo que significa que no obtendremos una advertencia o un error si violamos esa regla.
Cree un archivo llamado .prettierrc y agregue el siguiente código:
{ "trailingComma": "es5", "tabWidth": 2, "semi": true, "singleQuote": true }
Estamos configurando un ancho de tabulación de 2
y aplicando el uso de comillas simples en toda nuestra aplicación. Consulte la guía más bonita para obtener más opciones de estilo.
Ahora agregue los siguientes scripts a su paquete.json :
# add these one after the other "lint": "./node_modules/.bin/eslint ./src" "pretty": "prettier --write '**/*.{js,json}' '!node_modules/**'" "postpretty": "yarn lint --fix"
Ejecute yarn lint
. Debería ver una serie de errores y advertencias en la consola.
El comando pretty
embellece nuestro código. El comando postpretty
se ejecuta inmediatamente después. Ejecuta el comando lint
con el indicador --fix
agregado. Esta bandera le dice a ESLint
que solucione automáticamente los problemas comunes de pelusa. De esta manera, principalmente ejecuto el comando yarn pretty
sin preocuparme por el comando lint
.
Ejecutar yarn pretty
. Debería ver que solo tenemos dos advertencias sobre la presencia de una alert
en el archivo bin/www.js .
Así es como se ve la estructura de nuestro proyecto en este punto.
EXPRESS-API-TEMPLATE ├── build ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── nodemon.json ├── package.json ├── README.md └── yarn.lock
Puede encontrar que tiene un archivo adicional, yarn-error.log
en la raíz de su proyecto. Agréguelo al archivo .gitignore
. Confirme sus cambios.
- La rama correspondiente en este punto de mi repositorio es 02-dev-dependencies.
Configuración y variables de entorno en nuestro archivo .env
En casi todos los proyectos, necesitará un lugar para almacenar la configuración que se utilizará en toda su aplicación, por ejemplo, una clave secreta de AWS. Almacenamos tales configuraciones como variables de entorno. Esto los mantiene alejados de miradas indiscretas y podemos usarlos dentro de nuestra aplicación según sea necesario.
Me gusta tener un archivo settings.js con el que leo todas mis variables de entorno. Luego, puedo consultar el archivo de configuración desde cualquier lugar dentro de mi aplicación. Tiene la libertad de nombrar este archivo como desee, pero existe cierto consenso sobre el nombre de dichos archivos settings.js o config.js .
Para nuestras variables de entorno, las mantendremos en un archivo .env
y las leeremos en nuestro archivo de settings
desde allí.
Cree el archivo .env en la raíz de su proyecto e ingrese la siguiente línea:
TEST_ENV_VARIABLE="Environment variable is coming across"
Para poder leer variables de entorno en nuestro proyecto, hay una buena biblioteca, dotenv
, que lee nuestro archivo .env
y nos da acceso a las variables de entorno definidas en su interior. Vamos a instalarlo.
# install dotenv yarn add dotenv
Agregue el archivo .env a la lista de archivos que nodemon
está viendo.
Ahora, cree el archivo settings.js dentro de la carpeta src/
y agregue el siguiente código:
import dotenv from 'dotenv'; dotenv.config(); export const testEnvironmentVariable = process.env.TEST_ENV_VARIABLE;
Importamos el paquete dotenv
y llamamos a su método de configuración. Luego exportamos testEnvironmentVariable
que configuramos en nuestro archivo .env
.
Abra src/routes/index.js y reemplace el código con el siguiente.
import express from 'express'; import { testEnvironmentVariable } from '../settings'; const indexRouter = express.Router(); indexRouter.get('/', (req, res) => res.status(200).json({ message: testEnvironmentVariable })); export default indexRouter;
El único cambio que hemos hecho aquí es que importamos testEnvironmentVariable
de nuestro archivo de settings
y lo usamos como mensaje de respuesta para una solicitud de la ruta /
.
Visite https://localhost:3000/v1 y debería ver el mensaje, como se muestra a continuación.
{ "message": "Environment variable is coming across." }
Y eso es. A partir de ahora podemos agregar tantas variables de entorno como queramos y podemos exportarlas desde nuestro archivo settings.js .
Este es un buen punto para confirmar su código. Recuerda embellecer y eliminar tu código.
- La rama correspondiente en mi repositorio es 03-env-variables.
Escribiendo nuestra primera prueba
Es hora de incorporar pruebas en nuestra aplicación. Una de las cosas que dan confianza al desarrollador en su código son las pruebas. Estoy seguro de que ha visto innumerables artículos en la web que predican el desarrollo basado en pruebas (TDD). No se puede enfatizar lo suficiente que su código necesita alguna medida de prueba. TDD es muy fácil de seguir cuando trabaja con Express.js.
En nuestras pruebas, haremos llamadas a nuestros puntos finales de API y verificaremos si lo que se devuelve es lo que esperamos.
Instala las dependencias requeridas:
# install dependencies yarn add mocha chai nyc sinon-chai supertest coveralls --dev
Cada una de estas bibliotecas tiene su propio papel que desempeñar en nuestras pruebas.
mocha | corredor de pruebas |
chai | se utiliza para hacer afirmaciones |
nyc | recopilar informe de cobertura de prueba |
sinon-chai | extiende las afirmaciones de chai |
supertest | se utiliza para realizar llamadas HTTP a nuestros puntos finales de API |
coveralls | por subir la cobertura de prueba a coveralls.io |
Cree una nueva test/
carpeta en la raíz de su proyecto. Cree dos archivos dentro de esta carpeta:
- prueba/configuración.js
- prueba/index.prueba.js
Mocha encontrará la carpeta test/
automáticamente.
Abra test/setup.js y pegue el siguiente código. Este es solo un archivo auxiliar que nos ayuda a organizar todas las importaciones que necesitamos en nuestros archivos de prueba.
import supertest from 'supertest'; import chai from 'chai'; import sinonChai from 'sinon-chai'; import app from '../src/app'; chai.use(sinonChai); export const { expect } = chai; export const server = supertest.agent(app); export const BASE_URL = '/v1';
Esto es como un archivo de configuración, pero para nuestras pruebas. De esta manera no tenemos que inicializar todo dentro de cada uno de nuestros archivos de prueba. Así que importamos los paquetes necesarios y exportamos lo que inicializamos, que luego podemos importar en los archivos que los necesitan.
Abra index.test.js y pegue el siguiente código de prueba.
import { expect, server, BASE_URL } from './setup'; describe('Index page test', () => { it('gets base url', done => { server .get(`${BASE_URL}/`) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.message).to.equal( 'Environment variable is coming across.' ); done(); }); }); });
Aquí hacemos una solicitud para obtener el punto final base, que es /
y afirmamos que el res.
el objeto body
tiene una clave de message
con un valor de Environment variable is coming across.
Si no está familiarizado con el patrón describe
, it
animo a que eche un vistazo rápido al documento "Primeros pasos" de Mocha.
Agregue el comando de prueba a la sección de scripts
de package.json .
"test": "nyc --reporter=html --reporter=text --reporter=lcov mocha -r @babel/register"
Este script ejecuta nuestra prueba con nyc
y genera tres tipos de informes de cobertura: un informe HTML, enviado a la carpeta coverage/
; un informe de texto enviado a la terminal y un informe lcov enviado a la carpeta .nyc_output/
.
Ahora ejecute yarn test
. Debería ver un informe de texto en su terminal como el de la foto de abajo.
Observe que se generan dos carpetas adicionales:
-
.nyc_output/
-
coverage/
Mire dentro .gitignore
y verá que ya estamos ignorando ambos. Le animo a abrir la coverage/index.html
en un navegador y ver el informe de prueba para cada archivo.
Este es un buen punto para confirmar los cambios.
- La rama correspondiente en mi repositorio es 04-first-test.
Integración continua (CD) e insignias: Travis, Overol, Code Climate, AppVeyor
Ahora es el momento de configurar las herramientas de integración e implementación continuas (CI/CD). Configuraremos servicios comunes como travis-ci
, coveralls
, AppVeyor
y codeclimate
y agregaremos insignias a nuestro archivo README.
Empecemos.
Travis CI
Travis CI es una herramienta que ejecuta nuestras pruebas automáticamente cada vez que enviamos una confirmación a GitHub (y recientemente, a Bitbucket) y cada vez que creamos una solicitud de extracción. Esto es principalmente útil cuando se realizan solicitudes de extracción al mostrarnos si nuestro nuevo código ha roto alguna de nuestras pruebas.
- Visite travis-ci.com o travis-ci.org y cree una cuenta si no tiene una. Tienes que registrarte con tu cuenta de GitHub.
- Pase el cursor sobre la flecha desplegable junto a su foto de perfil y haga clic en
settings
. - En la pestaña
Repositories
, haga clic enManage repositories on Github
para ser redirigido a Github. - En la página de GitHub, desplácese hacia abajo hasta
Repository access
y haga clic en la casilla de verificación junto aOnly select repositories
. - Haga clic en el menú desplegable
Select repositories
y busque el repositorioexpress-api-template
. Haga clic en él para agregarlo a la lista de repositorios que desea agregar atravis-ci
. - Haga clic en
Approve and install
y espere a que se le redirija de nuevo atravis-ci
. - En la parte superior de la página del repositorio, cerca del nombre del repositorio, haga clic en el icono de
build unknown
. En el modal Imagen de estado, seleccione Markdown en el menú desplegable de formato. - Copie el código resultante y péguelo en su archivo README.md .
- En la página del proyecto, haga clic en
More options
>Settings
. En la secciónEnvironment Variables
, agregue la variable deTEST_ENV_VARIABLE
. Al ingresar su valor, asegúrese de tenerlo entre comillas dobles como esta"Environment variable is coming across."
- Cree un archivo .travis.yml en la raíz de su proyecto y péguelo en el siguiente código (estableceremos el valor de
CC_TEST_REPORTER_ID
en la sección Code Climate).
language: node_js env: global: - CC_TEST_REPORTER_ID=get-this-from-code-climate-repo-page matrix: include: - node_js: '12' cache: directories: [node_modules] install: yarn after_success: yarn coverage before_script: - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build script: - yarn test after_script: - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESUL
Primero, le decimos a Travis que ejecute nuestra prueba con Node.js, luego configure la variable de entorno global CC_TEST_REPORTER_ID
(hablaremos de esto en la sección Code Climate). En la sección de matrix
, le decimos a Travis que ejecute nuestras pruebas con Node.js v12. También queremos almacenar en caché el directorio node_modules/
para que no tenga que regenerarse cada vez.
Instalamos nuestras dependencias usando el comando yarn
, que es una abreviatura de yarn install
. Los comandos before_script
y after_script
se utilizan para cargar los resultados de cobertura en codeclimate
. Configuraremos codeclimate
breve. Después yarn test
ejecute con éxito, también queremos ejecutar la yarn coverage
que cargará nuestro informe de cobertura en coveralls.io.
overoles
Coveralls carga datos de cobertura de prueba para una fácil visualización. Podemos ver la cobertura de prueba en nuestra máquina local desde la carpeta de cobertura, pero Coveralls la pone a disposición fuera de nuestra máquina local.
- Visite coveralls.io e inicie sesión o regístrese con su cuenta de Github.
- Pase el cursor sobre el lado izquierdo de la pantalla para revelar el menú de navegación. Haga clic en
ADD REPOS
. - Busque el repositorio
express-api-template
y active la cobertura con el botón de alternar en el lado izquierdo. Si no puede encontrarlo, haga clic enSYNC REPOS
en la esquina superior derecha y vuelva a intentarlo. Tenga en cuenta que su repositorio debe ser público, a menos que tenga una cuenta PRO. - Haga clic en detalles para ir a la página de detalles del repositorio.
- Cree el archivo .coveralls.yml en la raíz de su proyecto e ingrese el código a continuación. Para obtener el
repo_token
, haga clic en los detalles del repositorio. Lo encontrarás fácilmente en esa página. Simplemente podría hacer una búsqueda en el navegador pararepo_token
.
repo_token: get-this-from-repo-settings-on-coveralls.io
Este token asigna sus datos de cobertura a un repositorio en Coveralls. Ahora, agregue el comando de coverage
a la sección de secuencias de scripts
de su archivo package.json :
"coverage": "nyc report --reporter=text-lcov | coveralls"
Este comando carga el informe de cobertura en la carpeta .nyc_output
a coveralls.io. Encienda su conexión a Internet y ejecute:
yarn coverage
Esto debería cargar el informe de cobertura existente en overoles. Actualice la página del informe sobre overoles para ver el informe completo.
En la página de detalles, desplácese hacia abajo para encontrar la sección BADGE YOUR REPO
. Haga clic en el menú desplegable EMBED
y copie el código de descuento y péguelo en su archivo README .
Código Clima
Code Climate es una herramienta que nos ayuda a medir la calidad del código. Nos muestra métricas de mantenimiento comparando nuestro código con algunos patrones definidos. Detecta cosas como repeticiones innecesarias y bucles profundamente anidados. También recopila datos de cobertura de prueba al igual que coveralls.io.
- Visite codeclimate.com y haga clic en 'Registrarse con GitHub'. Inicia sesión si ya tienes una cuenta.
- Una vez en tu tablero, haz clic en
Add a repository
. - Busque el repositorio
express-api-template
en la lista y haga clic enAdd Repo
repositorio. - Espere a que se complete la compilación y redirija al panel de control del repositorio.
- En
Codebase Summary
de la base de código, haga clic enTest Coverage
. En el menúTest coverage
, copie elTEST REPORTER ID
y péguelo en su .travis.yml como el valor deCC_TEST_REPORTER_ID
. - Todavía en la misma página, en la barra de navegación de la izquierda, en
EXTRAS
, haga clic en Insignias. Copie las insignias demaintainability
ytest coverage
en formato de descuento y péguelas en su archivo README.md .
Es importante tener en cuenta que hay dos formas de configurar las comprobaciones de mantenimiento. Existen configuraciones predeterminadas que se aplican a cada repositorio, pero si lo desea, puede proporcionar un archivo .codeclimate.yml en la raíz de su proyecto. Usaré la configuración predeterminada, que puede encontrar en la pestaña Maintainability
de la página de configuración del repositorio. Os animo a echar un vistazo al menos. Si aún desea configurar sus propios ajustes, esta guía le brindará toda la información que necesita.
AppVeyor
AppVeyor y Travis CI son ejecutores de pruebas automatizados. La principal diferencia es que travis-ci ejecuta pruebas en un entorno Linux mientras que AppVeyor ejecuta pruebas en un entorno Windows. Esta sección se incluye para mostrar cómo comenzar con AppVeyor.
- Visite AppVeyor e inicie sesión o regístrese.
- En la página siguiente, haga clic en
NEW PROJECT
. - En la lista de repositorios, busque el repositorio
express-api-template
. Pase el cursor sobre él y haga clic enADD
. - Haga clic en la pestaña
Settings
. Haga clic enEnvironment
en la barra de navegación de la izquierda. AgregueTEST_ENV_VARIABLE
y su valor. Haga clic en 'Guardar' en la parte inferior de la página. - Cree el archivo appveyor.yml en la raíz de su proyecto y pegue el código a continuación.
environment: matrix: - nodejs_version: "12" install: - yarn test_script: - yarn test build: off
Este código le indica a AppVeyor que ejecute nuestras pruebas con Node.js v12. Luego instalamos las dependencias de nuestro proyecto con el comando yarn
. test_script
especifica el comando para ejecutar nuestra prueba. La última línea le dice a AppVeyor que no cree una carpeta de compilación.
Haga clic en la pestaña Settings
. En la barra de navegación de la izquierda, haga clic en insignias. Copie el código de descuento y péguelo en su archivo README.md .
Confirma tu código y envíalo a GitHub. Si ha hecho todo según las instrucciones, todas las pruebas deberían pasar y debería ver sus nuevas insignias brillantes como se muestra a continuación. Verifique nuevamente que haya configurado las variables de entorno en Travis y AppVeyor.
Ahora es un buen momento para confirmar nuestros cambios.
- La rama correspondiente en mi repositorio es 05-ci.
Agregar un controlador
Actualmente, estamos manejando la solicitud GET
a la URL raíz, /v1
, dentro de src/routes/index.js . Esto funciona como se esperaba y no tiene nada de malo. Sin embargo, a medida que crece su aplicación, desea mantener las cosas ordenadas. Desea que las preocupaciones estén separadas: desea una separación clara entre el código que maneja la solicitud y el código que genera la respuesta que se enviará al cliente. Para lograr esto, escribimos controllers
. Los controladores son simplemente funciones que manejan las solicitudes que llegan a través de una URL en particular.
Para comenzar, cree una carpeta controllers/
dentro de la carpeta src/
. Los controllers
internos crean dos archivos: index.js y home.js. Exportaríamos nuestras funciones desde index.js . Puede nombrar home.js como desee, pero normalmente desea nombrar los controladores según lo que controlan. Por ejemplo, puede tener un archivo usersController.js para contener todas las funciones relacionadas con los usuarios en su aplicación.
Abra src/controllers/home.js e ingrese el siguiente código:
import { testEnvironmentVariable } from '../settings'; export const indexPage = (req, res) => res.status(200).json({ message: testEnvironmentVariable });
Notará que solo movimos la función que maneja la solicitud de la ruta /
.
Abra src/controllers/index.js e ingrese el siguiente código.
// export everything from home.js export * from './home';
Exportamos todo desde el archivo home.js. Esto nos permite acortar nuestras instrucciones de importación para import { indexPage } from '../controllers';
Abra src/routes/index.js y reemplace el código allí con el siguiente:
import express from 'express'; import { indexPage } from '../controllers'; const indexRouter = express.Router(); indexRouter.get('/', indexPage); export default indexRouter;
El único cambio aquí es que proporcionamos una función para manejar la solicitud a la ruta /
.
Acabas de escribir con éxito tu primer controlador. A partir de aquí, es cuestión de agregar más archivos y funciones según sea necesario.
Continúe y juegue con la aplicación agregando algunas rutas y controladores más. Puede agregar una ruta y un controlador para la página Acerca de. Sin embargo, recuerda actualizar tu prueba.
Ejecute la yarn test
para confirmar que no hemos roto nada. ¿Pasa tu prueba? Eso es genial.
Este es un buen punto para confirmar nuestros cambios.
- La rama correspondiente en mi repositorio es 06-controllers.
Conexión de la base de datos PostgreSQL
y escritura de un modelo
Nuestro controlador actualmente devuelve mensajes de texto codificados. En una aplicación del mundo real, a menudo necesitamos almacenar y recuperar información de una base de datos. En esta sección, conectaremos nuestra aplicación a una base de datos PostgreSQL.
Vamos a implementar el almacenamiento y recuperación de mensajes de texto simples usando una base de datos. Tenemos dos opciones para configurar una base de datos: podemos aprovisionar una desde un servidor en la nube o podemos configurar la nuestra propia localmente.
Le recomendaría aprovisionar una base de datos desde un servidor en la nube. ElephantSQL has a free plan that gives 20MB of free storage which is sufficient for this tutorial. Visit the site and click on Get a managed database today
. Create an account (if you don't have one) and follow the instructions to create a free plan. Take note of the URL on the database details page. We'll be needing it soon.
If you would rather set up a database locally, you should visit the PostgreSQL and PgAdmin sites for further instructions.
Once we have a database set up, we need to find a way to allow our Express app to communicate with our database. Node.js by default doesn't support reading and writing to PostgreSQL
database, so we'll be using an excellent library, appropriately named, node-postgres.
node-postgres
executes SQL
queries in node and returns the result as an object, from which we can grab items from the rows key.
Let's connect node-postgres
to our application.
# install node-postgres yarn add pg
Open settings.js and add the line below:
export const connectionString = process.env.CONNECTION_STRING;
Open your .env
file and add the CONNECTION_STRING
variable. This is the connection string we'll be using to establish a connection to our database. The general form of the connection string is shown below.
CONNECTION_STRING="postgresql://dbuser:dbpassword@localhost:5432/dbname"
If you're using elephantSQL you should copy the URL from the database details page.
Inside your /src
folder, create a new folder called models/
. Inside this folder, create two files:
- pool.js
- model.js
Open pools.js and paste the following code:
import { Pool } from 'pg'; import dotenv from 'dotenv'; import { connectionString } from '../settings'; dotenv.config(); export const pool = new Pool({ connectionString });
First, we import the Pool
and dotenv
from the pg
and dotenv
packages, and then import the settings we created for our postgres database before initializing dotenv
. We establish a connection to our database with the Pool
object. In node-postgres
, every query is executed by a client. A Pool is a collection of clients for communicating with the database.
To create the connection, the pool constructor takes a config object. You can read more about all the possible configurations here. It also accepts a single connection string, which I will use here.
Open model.js and paste the following code:
import { pool } from './pool'; class Model { constructor(table) { this.pool = pool; this.table = table; this.pool.on('error', (err, client) => `Error, ${err}, on idle client${client}`); } async select(columns, clause) { let query = `SELECT ${columns} FROM ${this.table}`; if (clause) query += clause; return this.pool.query(query); } } export default Model;
We create a model class whose constructor accepts the database table we wish to operate on. We'll be using a single pool for all our models.
We then create a select
method which we will use to retrieve items from our database. This method accepts the columns we want to retrieve and a clause, such as a WHERE
clause. It returns the result of the query, which is a Promise
. Remember we said earlier that every query is executed by a client, but here we execute the query with pool. This is because, when we use pool.query
, node-postgres
executes the query using the first available idle client.
The query you write is entirely up to you, provided it is a valid SQL
statement that can be executed by a Postgres engine.
The next step is to actually create an API endpoint to utilize our newly connected database. Before we do that, I'd like us to create some utility functions. The goal is for us to have a way to perform common database operations from the command line.
Create a folder, utils/
inside the src/
folder. Create three files inside this folder:
- queries.js
- queryFunctions.js
- runQuery.js
We're going to create functions to create a table in our database, insert seed data in the table, and to delete the table.
Abre queries.js y pega el siguiente código:
export const createMessageTable = ` DROP TABLE IF EXISTS messages; CREATE TABLE IF NOT EXISTS messages ( id SERIAL PRIMARY KEY, name VARCHAR DEFAULT '', message VARCHAR NOT NULL ) `; export const insertMessages = ` INSERT INTO messages(name, message) VALUES ('chidimo', 'first message'), ('orji', 'second message') `; export const dropMessagesTable = 'DROP TABLE messages';
En este archivo, definimos tres cadenas de consulta SQL. La primera consulta elimina y vuelve a crear la tabla de messages
. La segunda consulta inserta dos filas en la tabla de messages
. Siéntase libre de agregar más artículos aquí. La última consulta descarta/elimina la tabla de messages
.
Abra queryFunctions.js y pegue el siguiente código:
import { pool } from '../models/pool'; import { insertMessages, dropMessagesTable, createMessageTable, } from './queries'; export const executeQueryArray = async arr => new Promise(resolve => { const stop = arr.length; arr.forEach(async (q, index) => { await pool.query(q); if (index + 1 === stop) resolve(); }); }); export const dropTables = () => executeQueryArray([ dropMessagesTable ]); export const createTables = () => executeQueryArray([ createMessageTable ]); export const insertIntoTables = () => executeQueryArray([ insertMessages ]);
Aquí, creamos funciones para ejecutar las consultas que definimos anteriormente. Tenga en cuenta que la función executeQueryArray
ejecuta una matriz de consultas y espera a que cada una se complete dentro del bucle. (Sin embargo, no haga tal cosa en el código de producción). Entonces, solo resolvemos la promesa una vez que hayamos ejecutado la última consulta en la lista. La razón para usar una matriz es que la cantidad de tales consultas crecerá a medida que crezca la cantidad de tablas en nuestra base de datos.
Abra runQuery.js y pegue el siguiente código:
import { createTables, insertIntoTables } from './queryFunctions'; (async () => { await createTables(); await insertIntoTables(); })();
Aquí es donde ejecutamos las funciones para crear la tabla e insertar los mensajes en la tabla. Agreguemos un comando en la sección de scripts
de nuestro paquete.json para ejecutar este archivo.
"runQuery": "babel-node ./src/utils/runQuery"
Ahora ejecuta:
yarn runQuery
Si inspecciona su base de datos, verá que se ha creado la tabla de messages
y que los mensajes se insertaron en la tabla.
Si está utilizando ElephantSQL, en la página de detalles de la base de datos, haga clic en BROWSER
en el menú de navegación izquierdo. Seleccione la tabla de messages
y haga clic en Execute
. Debería ver los mensajes del archivo queries.js .
Vamos a crear un controlador y una ruta para mostrar los mensajes de nuestra base de datos.
Cree un nuevo archivo de controlador src/controllers/messages.js y pegue el siguiente código:
import Model from '../models/model'; const messagesModel = new Model('messages'); export const messagesPage = async (req, res) => { try { const data = await messagesModel.select('name, message'); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); } };
Importamos nuestra clase Model
y creamos una nueva instancia de ese modelo. Esto representa la tabla de messages
en nuestra base de datos. Luego usamos el método de select
del modelo para consultar nuestra base de datos. Los datos ( name
y message
) que recibimos se envían como JSON en la respuesta.
Definimos el controlador de la página de messagesPage
como una función async
. Dado que las consultas node-postgres
devuelven una promesa, await
el resultado de esa consulta. Si encontramos un error durante la consulta, lo detectamos y mostramos la pila al usuario. Debe decidir cómo elegir manejar el error.
Agregue el extremo de obtención de mensajes a src/routes/index.js y actualice la línea de importación.
# update the import line import { indexPage, messagesPage } from '../controllers'; # add the get messages endpoint indexRouter.get('/messages', messagesPage)
Visite https://localhost:3000/v1/messages y debería ver los mensajes que se muestran a continuación.
Ahora, actualicemos nuestro archivo de prueba. Al hacer TDD, generalmente escribe sus pruebas antes de implementar el código que hace que pase la prueba. Estoy tomando el enfoque opuesto aquí porque todavía estamos trabajando en configurar la base de datos.
Cree un nuevo archivo, hooks.js en la carpeta test/
e ingrese el siguiente código:
import { dropTables, createTables, insertIntoTables, } from '../src/utils/queryFunctions'; before(async () => { await createTables(); await insertIntoTables(); }); after(async () => { await dropTables(); });
Cuando comienza nuestra prueba, Mocha encuentra este archivo y lo ejecuta antes de ejecutar cualquier archivo de prueba. Ejecuta el enlace before
para crear la base de datos e insertar algunos elementos en ella. Los archivos de prueba se ejecutan después de eso. Una vez finalizada la prueba, Mocha ejecuta el enlace after
en el que soltamos la base de datos. Esto asegura que cada vez que ejecutamos nuestras pruebas, lo hacemos con registros limpios y nuevos en nuestra base de datos.
Cree un nuevo archivo de prueba test/messages.test.js y agregue el siguiente código:
import { expect, server, BASE_URL } from './setup'; describe('Messages', () => { it('get messages page', done => { server .get(`${BASE_URL}/messages`) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.messages).to.be.instanceOf(Array); res.body.messages.forEach(m => { expect(m).to.have.property('name'); expect(m).to.have.property('message'); }); done(); }); }); });
Afirmamos que el resultado de la llamada a /messages
es una matriz. Para cada objeto de mensaje, afirmamos que tiene el name
y la propiedad del message
.
El último paso en esta sección es actualizar los archivos de CI.
Agregue las siguientes secciones al archivo .travis.yml :
services: - postgresql addons: postgresql: "10" apt: packages: - postgresql-10 - postgresql-client-10 before_install: - sudo cp /etc/postgresql/{9.6,10}/main/pg_hba.conf - sudo /etc/init.d/postgresql restart
Esto le indica a Travis que active una base de datos PostgreSQL 10 antes de ejecutar nuestras pruebas.
Agregue el comando para crear la base de datos como la primera entrada en la sección before_script
:
# add this as the first line in the before_script section - psql -c 'create database testdb;' -U postgres
Cree la variable de entorno CONNECTION_STRING
en Travis y use el siguiente valor:
CONNECTION_STRING="postgresql://postgres:postgres@localhost:5432/testdb"
Agregue las siguientes secciones al archivo .appveyor.yml :
before_test: - SET PGUSER=postgres - SET PGPASSWORD=Password12! - PATH=C:\Program Files\PostgreSQL\10\bin\;%PATH% - createdb testdb services: - postgresql101
Agregue la variable de entorno de la cadena de conexión a appveyor. Utilice la siguiente línea:
CONNECTION_STRING=postgresql://postgres:Password12!@localhost:5432/testdb
Ahora confirme sus cambios y empuje a GitHub. Sus pruebas deben pasar tanto en Travis CI como en AppVeyor.
- La rama correspondiente en mi repositorio es 07-connect-postgres.
Nota : espero que todo funcione bien por tu parte, pero en caso de que tengas problemas por algún motivo, ¡siempre puedes consultar mi código en el repositorio!
Ahora, veamos cómo podemos agregar un mensaje a nuestra base de datos. Para este paso, necesitaremos una forma de enviar solicitudes POST
a nuestra URL. Usaré Postman para enviar solicitudes POST
.
Sigamos la ruta TDD y actualicemos nuestra prueba para reflejar lo que esperamos lograr.
Abra test/message.test.js y agregue el siguiente caso de prueba:
it('posts messages', done => { const data = { name: 'some name', message: 'new message' }; server .post(`${BASE_URL}/messages`) .send(data) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.messages).to.be.instanceOf(Array); res.body.messages.forEach(m => { expect(m).to.have.property('id'); expect(m).to.have.property('name', data.name); expect(m).to.have.property('message', data.message); }); done(); }); });
Esta prueba realiza una solicitud POST al punto final /v1/messages
y esperamos que se devuelva una matriz. También verificamos las propiedades id
, name
y message
en la matriz.
Ejecute sus pruebas para ver que este caso falla. Ahora arreglemoslo.
Para enviar solicitudes de publicación, utilizamos el método de publicación del servidor. También enviamos el nombre y el mensaje que queremos insertar. Esperamos que la respuesta sea una matriz, con una id
de propiedad y la otra información que conforma la consulta. La id
es prueba de que se ha insertado un registro en la base de datos.
Abra src/models/model.js y agregue el método de insert
:
async insertWithReturn(columns, values) { const query = ` INSERT INTO ${this.table}(${columns}) VALUES (${values}) RETURNING id, ${columns} `; return this.pool.query(query); }
Este es el método que nos permite insertar mensajes en la base de datos. Después de insertar el elemento, devuelve la id
, el name
y message
.
Abra src/controllers/messages.js y agregue el siguiente controlador:
export const addMessage = async (req, res) => { const { name, message } = req.body; const columns = 'name, message'; const values = `'${name}', '${message}'`; try { const data = await messagesModel.insertWithReturn(columns, values); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); } };
Desestructuramos el cuerpo de la solicitud para obtener el nombre y el mensaje. Luego usamos los valores para formar una cadena de consulta SQL que luego ejecutamos con el método insertWithReturn
de nuestro modelo.
Agregue el siguiente punto final POST
a /src/routes/index.js y actualice su línea de importación.
import { indexPage, messagesPage, addMessage } from '../controllers'; indexRouter.post('/messages', addMessage);
Ejecute sus pruebas para ver si pasan.
Abra Postman y envíe una solicitud POST
al punto final de messages
. Si acaba de ejecutar su prueba, recuerde ejecutar la yarn query
para recrear la tabla de messages
.
yarn query
Confirma tus cambios y envíalos a GitHub. Sus pruebas deben pasar tanto en Travis como en AppVeyor. La cobertura de su prueba se reducirá en algunos puntos, pero está bien.
- La rama correspondiente en mi repositorio es 08-post-to-db.
software intermedio
Nuestra discusión sobre Express no estará completa sin hablar sobre el middleware. La documentación de Express describe un middleware como:
“[...] funciones que tienen acceso al objeto de solicitud (req
), el objeto de respuesta (res
) y la siguiente función de middleware en el ciclo de solicitud-respuesta de la aplicación. La siguiente función de middleware se indica comúnmente mediante una variable denominadanext
.
Un middleware puede realizar cualquier cantidad de funciones, como la autenticación, la modificación del cuerpo de la solicitud, etc. Consulte la documentación de Express sobre el uso de middleware.
Vamos a escribir un middleware simple que modifique el cuerpo de la solicitud. Nuestro middleware agregará la palabra SAYS:
al mensaje entrante antes de que se guarde en la base de datos.
Antes de comenzar, modifiquemos nuestra prueba para reflejar lo que queremos lograr.
Abra test/messages.test.js y modifique la última línea esperada en el caso de prueba de posts message
de publicaciones:
it('posts messages', done => { ... expect(m).to.have.property('message', `SAYS: ${data.message}`); # update this line ... });
Estamos afirmando que la cadena SAYS:
se ha agregado al mensaje. Ejecute sus pruebas para asegurarse de que este caso de prueba falle.
Ahora, escribamos el código para que pase la prueba.
Cree una nueva carpeta middleware/
dentro de la carpeta src/
. Cree dos archivos dentro de esta carpeta:
- middleware.js
- índice.js
Ingrese el siguiente código en middleware.js :
export const modifyMessage = (req, res, next) => { req.body.message = `SAYS: ${req.body.message}`; next(); };
Aquí, agregamos la cadena SAYS:
al mensaje en el cuerpo de la solicitud. Después de hacer eso, debemos llamar a la función next()
para pasar la ejecución a la siguiente función en la cadena de solicitud-respuesta. Cada middleware tiene que llamar a la next
función para pasar la ejecución al siguiente middleware en el ciclo de solicitud-respuesta.
Ingrese el siguiente código en index.js :
# export everything from the middleware file export * from './middleware';
Esto exporta el middleware que tenemos en el archivo /middleware.js . Por ahora, solo tenemos el middleware modifyMessage
.
Abra src/routes/index.js y agregue el middleware a la cadena de solicitud-respuesta del mensaje posterior.
import { modifyMessage } from '../middleware'; indexRouter.post('/messages', modifyMessage, addMessage);
Podemos ver que la función modificarMensaje viene antes addMessage
modifyMessage
Invocamos la función addMessage
llamando a next
en el middleware modifyMessage
. Como experimento, comente la línea next()
en el medio modifyMessage
y observe cómo se bloquea la solicitud.
Abra Postman y cree un nuevo mensaje. Debería ver la cadena adjunta.
Este es un buen punto para confirmar nuestros cambios.
- La rama correspondiente en mi repositorio es 09-middleware.
Manejo de errores y middleware asíncrono
Los errores son inevitables en cualquier aplicación. La tarea que tiene ante sí el desarrollador es cómo tratar los errores con la mayor gracia posible.
En expreso:
“El manejo de errores se refiere a cómo Express detecta y procesa los errores que ocurren tanto sincrónicamente como asincrónicamente.
Si solo estuviéramos escribiendo funciones sincrónicas, es posible que no tengamos que preocuparnos tanto por el manejo de errores, ya que Express ya hace un excelente trabajo al manejarlos. Según los documentos:
“Los errores que ocurren en el código síncrono dentro de los controladores de ruta y el middleware no requieren trabajo adicional”.
Pero una vez que comenzamos a escribir controladores de enrutadores asincrónicos y middleware, tenemos que hacer un manejo de errores.
Nuestro middleware modifyMessage
es una función síncrona. Si ocurre un error en esa función, Express lo manejará perfectamente. Veamos cómo tratamos los errores en el middleware asíncrono.
Digamos que, antes de crear un mensaje, queremos obtener una imagen de la API de Lorem Picsum usando esta URL https://picsum.photos/id/0/info
. Esta es una operación asíncrona que podría tener éxito o fallar, y eso presenta un caso que debemos abordar.
Comience instalando Axios.
# install axios yarn add axios
Abra src/middleware/middleware.js y agregue la siguiente función:
export const performAsyncAction = async (req, res, next) => { try { await axios.get('https://picsum.photos/id/0/info'); next(); } catch (err) { next(err); } };
En esta función async
, await
una llamada a una API (en realidad no necesitamos los datos devueltos) y luego llamamos a la next
función en la cadena de solicitud. Si la solicitud falla, detectamos el error y lo pasamos a next
. Una vez que Express ve este error, omite todos los demás middleware de la cadena. Si no llamamos a next(err)
, la solicitud se colgará. Si solo llamamos a next()
sin err
, la solicitud procederá como si nada y no se detectará el error.
Importe esta función y agréguela a la cadena de middleware de la ruta de mensajes posteriores:
import { modifyMessage, performAsyncAction } from '../middleware'; indexRouter.post('/messages', modifyMessage, performAsyncAction, addMessage);
Abra src/app.js y agregue el siguiente código justo antes de la línea de export default app
.
app.use((err, req, res, next) => { res.status(400).json({ error: err.stack }); }); export default app;
Este es nuestro controlador de errores. De acuerdo con el documento de manejo de errores Express:
"[...] las funciones de manejo de errores tienen cuatro argumentos en lugar de tres: (err, req, res, next)
".
Tenga en cuenta que este controlador de errores debe ser el último, después de cada llamada app.use()
. Una vez que encontramos un error, devolvemos el seguimiento de la pila con un código de estado de 400
. Puedes hacer lo que quieras con el error. Es posible que desee registrarlo o enviarlo a alguna parte.
Este es un buen lugar para confirmar sus cambios.
- La rama correspondiente en mi repositorio es 10-async-middleware.
Implementar en Heroku
- Para comenzar, vaya a https://www.heroku.com/ e inicie sesión o regístrese.
- Descargue e instale la CLI de Heroku desde aquí.
- Abra una terminal en la carpeta del proyecto para ejecutar el comando.
# login to heroku on command line heroku login
Esto abrirá una ventana del navegador y le pedirá que inicie sesión en su cuenta de Heroku.
Inicie sesión para otorgar a su terminal acceso a su cuenta de Heroku y cree una nueva aplicación de heroku ejecutando:
#app name is up to you heroku create app-name
Esto creará la aplicación en Heroku y devolverá dos URL.
# app production url and git url https://app-name.herokuapp.com/ | https://git.heroku.com/app-name.git
Copie la URL a la derecha y ejecute el siguiente comando. Tenga en cuenta que este paso es opcional, ya que es posible que Heroku ya haya agregado la URL remota.
# add heroku remote url git remote add heroku https://git.heroku.com/my-shiny-new-app.git
Abra una terminal lateral y ejecute el siguiente comando. Esto le muestra el inicio de sesión de la aplicación en tiempo real como se muestra en la imagen.
# see process logs heroku logs --tail
Ejecute los siguientes tres comandos para establecer las variables de entorno requeridas:
heroku config:set TEST_ENV_VARIABLE="Environment variable is coming across." heroku config:set CONNECTION_STRING=your-db-connection-string-here. heroku config:set NPM_CONFIG_PRODUCTION=false
Recuerde en nuestros scripts, establecemos:
"prestart": "babel ./src --out-dir build", "start": "node ./build/bin/www",
Para iniciar la aplicación, debe compilarse hasta ES5 usando prestart
en el paso previo al inicio porque babel solo existe en nuestras dependencias de desarrollo. Tenemos que establecer NPM_CONFIG_PRODUCTION
en false
para poder instalarlos también.
Para confirmar que todo está configurado correctamente, ejecute el siguiente comando. También puede visitar la pestaña de settings
en la página de la aplicación y hacer clic en Reveal Config Vars
.
# check configuration variables heroku config
Ahora ejecuta git push heroku
.
Para abrir la aplicación, ejecute:
# open /v1 route heroku open /v1 # open /v1/messages route heroku open /v1/messages
Si, como yo, está utilizando la misma base de datos de PostgresSQL tanto para el desarrollo como para la producción, puede encontrar que cada vez que ejecuta sus pruebas, la base de datos se elimina. Para recrearlo, puede ejecutar cualquiera de los siguientes comandos:
# run script locally yarn runQuery # run script with heroku heroku run yarn runQuery
Implementación continua (CD) con Travis
Ahora agreguemos la implementación continua (CD) para completar el flujo de CI/CD. Implementaremos desde Travis después de cada ejecución de prueba exitosa.
El primer paso es instalar Travis CI. (Puede encontrar las instrucciones de instalación aquí). Después de instalar con éxito Travis CI, inicie sesión ejecutando el siguiente comando. (Tenga en cuenta que esto debe hacerse en el repositorio de su proyecto).
# login to travis travis login --pro # use this if you're using two factor authentication travis login --pro --github-token enter-github-token-here
Si su proyecto está alojado en travis-ci.org, elimine la --pro
. Para obtener un token de GitHub, visite la página de configuración del desarrollador de su cuenta y genere uno. Esto solo se aplica si su cuenta está protegida con 2FA.
Abra su .travis.yml y agregue una sección de implementación:
deploy: provider: heroku app: master: app-name
Aquí, especificamos que queremos implementar en Heroku. La subsección de la aplicación especifica que queremos implementar la rama master
de nuestro repositorio en la app-name
aplicación en Heroku. Es posible implementar diferentes ramas para diferentes aplicaciones. Puedes leer más sobre las opciones disponibles aquí.
Ejecute el siguiente comando para cifrar su clave API de Heroku y agréguela a la sección de implementación:
# encrypt heroku API key and add to .travis.yml travis encrypt $(heroku auth:token) --add deploy.api_key --pro
Esto agregará la siguiente subsección a la sección de implementación.
api_key: secure: very-long-encrypted-api-key-string
Ahora confirme sus cambios y empuje a GitHub mientras monitorea sus registros. Verá que la compilación se activa tan pronto como finalice la prueba de Travis. De esta manera, si tenemos una prueba que falla, los cambios nunca se implementarán. Del mismo modo, si falla la compilación, fallará toda la ejecución de la prueba. Esto completa el flujo de CI/CD.
- La rama correspondiente en mi repositorio es 11-cd.
Conclusión
Si has llegado hasta aquí, digo: "¡Pulgares arriba!" En este tutorial, configuramos con éxito un nuevo proyecto Express. Seguimos adelante para configurar las dependencias de desarrollo, así como la integración continua (CI). Luego escribimos funciones asincrónicas para manejar las solicitudes a nuestros puntos finales de API, completadas con pruebas. Luego analizamos brevemente el manejo de errores. Finalmente, implementamos nuestro proyecto en Heroku y configuramos la Implementación continua.
Ahora tiene una plantilla para su próximo proyecto de back-end. Solo hemos hecho lo suficiente para que comience, pero debe seguir aprendiendo para continuar. Asegúrese de consultar también los documentos de Express.js. Si prefiere usar MongoDB
en lugar de PostgreSQL
, aquí tengo una plantilla que hace exactamente eso. Puedes comprobarlo para la configuración. Tiene solo algunos puntos de diferencia.
Recursos
- "Crear back-end de API Express con MongoDB", Orji Chidi Matthew, GitHub
- “Una breve guía para conectar el middleware”, Stephen Sugden
- "Plantilla API Express", GitHub
- “AppVeyor frente a Travis CI”, StackShare
- "El CLI de Heroku", Centro de desarrollo de Heroku
- “Despliegue de Heroku”, Travis CI
- "Uso de software intermedio", Express.js
- "Manejo de errores", Express.js
- “Primeros pasos”, Mocha
-
nyc
(GitHub) - ElefanteSQL
- Cartero
- Rápido
- Travis CI
- Código Clima
- postgresql
- pgAdmin