Express.js es un framework para crear aplicaciones web, apis y web services que ha sido inspirado en la librería Sinatra de Ruby. Es software libre y de código abierto con licencia MIT.
- Algunas de las características de Express.js:
- Es minimalista, lo que lo hace muy liviano ya que muchas de sus funcionalidades deben ser instaladas posteriormente a través de plugins y módulos adicionales.
- Incluye un sistema de template engines muy poderoso, con Handlebars, Pug, Mustach, etc. disponibles por defecto.
- Maneja Routing, con wildcards, parámetros, etc.
- Implementa middlewares, lo que es una de sus características más notables e importantes.
- Permite la instalación de plugins a travpes de middlewares.
🔗 Generate a New Set of Random Passwords and Keys
Para crear un servidor Express básico es necesario crear una carpeta para el proyecto, inicializar npm en este directorio e instalar Express.js como la primera dependencia de nuestro proyecto: npm i -S express
. Seguidamente creamos el index.js
con el código básico para requerir y ejecutar el servidor y la ruta base de nuestra aplicación.
Con el método .listen
inicializamos nuestro server en el puerto que elijamos. Este método devuelve el servidor como un objeto, que luego podemos utilizar para hacer un log con información que se mostrará por consola.
En el archivo package.json
definimos el script con el que arrancaremos nuestro server, el cual ejecutará la instrucción node index
, adicionalmente podemos definir un script para desarrollo en el que ejecutemos el index
con nodemon
que es una dependencia que ejecutará el servidor y actualizará su ejecución de manera automática cada vez que se hagan cambios en los archivos.
Archivos en 📂 ExpressJS/platzi-express-que-es-un-template-engine/hello-world
Finalmente con npm run dev
iniciamos el servidor y estamos listos para ver los resultados en el navegador, ya sea como html o como JSON.
En esta clase veremos cómo usar el generador de aplicaciones oficial de Express, llamado express-generator, que permite generar toda una estructura base o scafold predeterminada para iniciar un proyecto nuevo.
Se puede instalar para versinoes posteriores de node 8.2.0 "PREFERENTEMENTE" y dentro de una carpeta vacia con el nombre del proyecto
$ npx express-generator
Primero debemos instalarlo como una dependencia global con la instrucción para versiones anteriores de node:
npm i express-generator -g
Para generar una aplicación con el nombre de la aplicación(para versinoes posteriores de node 8.2.0), ejecutamos la instrucción para instalar el sistema de templates "PUG" PREFERENTEMENTE:
$ npx express-generator --view=pug <NOMBRE-DE-LA-APLICACION>
# OUTPUT
# SEGUIR LAS INSTRUCCIONES
change directory:
$ cd <NOMBRE-DE-LA-APLICACION>
install dependencies:
$ npm install
run the app:
$ DEBUG=<NOMBRE-DE-LA-APLICACION>:* npm start
Para versiones anteriores a node 8.2.0
express --view=pug <NOMBRE-DE-LA-APLICACION>
Finalmente seguimos las instrucciones que nos indica:
#Cambiar de directorio:
cd <NOMBRE-DE-LA-APLICACION>
#Instalar las dependencias:
npm install
#Ejecutar la app:
DEBUG=<NOMBRE-DE-LA-APLICACION>:* npm start
Con esta funcionalidad creamos una estructura inicial, predeterminada para un nuevo proyecto con Express.js, con algunas rutas y middlewares iniciales con los que podemos empezar a trabajar, ahorrándonos algunos pasos de configuración.
Un template engine es una implementación de software que nos permite mezclar datos con una plantilla o template con el fin de generar un documento que será renderizado en el navegador. Algunos de los template engines más conocidos son: Handlebars (para JavaScript), Twig y Blade (para Laravel / PHP), JSP (para Java) o Ninja (de python).
Express nos permite crear nuestro propio template engine personalizado. Además permite el uso de múltiples engines según la extensión de los archivos que le hayamos indicado.
app.engine('<extension>', function( filePath, options, callback ) {});
app.set('views', '<directorio-de-vistas>');
app.set('view engine', '<extension>');
Donde filePath
indica la ruta del template, options indica las variables que vamos a mezclar con el template y callback será la función que mezcle la plantilla con las opciones para obtener el documento que será mostrado en el navegador.
Algo importante que debemos notar es que en Node, los callbacks son “error first”, por lo que el primer parámetro devuelto por las funciones por lo general serán siempre el objeto que contenga los detalles del error, si es que los hay.
Además en el objeto options
recibido en el callback del engine, no solo se pasan las variables que definimos en la llamada, sino otros datos como settings
y locals
que deberemos descartar cuando estamos creando nuestro propio engine, pero que, cuando trabajamos con los template engines predefinidos, ya es Node quien se encarga de manejarlos.
Templates engine
La logica para el el manego de los templates propios es en el archivo /platzi-express-que-es-un-template-engine/template-engine/express-jsx.js
La logica para el el manego de los templates propios es en el archivo /platzi-express-que-es-un-template-engine/template-engine/express-jsx.js
const fs = require("fs");
function getKeysFromOptions(options) {
const { settings, _locals, ...objectKeys } = options;
return Object.keys(objectKeys);
}
function getRenderedContent(content, options) {
const keys = getKeysFromOptions(options);
let contentString = content.toString();
for (let key of keys) {
contentString = contentString.replace(
new RegExp(`\{${key}\}`, "gi"),
options[key]
);
}
return contentString;
}
function expressJxs(filePath, options, callback) {
fs.readFile(filePath, function(err, content) {
if (err) {
return callback(err);
}
const rendered = getRenderedContent(content, options);
return callback(null, rendered);
});
}
module.exports = expressJxs;
🔗 Mi repo en rama usando-pug-como-template
Para poder hacer uso de Handlebars en nuestro proyecto de Express.js lo único que debemos hacer es instalar como dependencia la librería consolidate y registrarla como engine.
Instalar consolidate
npm i consolidate
Como se muestra con el ejemplp de Express.js en platzi-express-que-es-un-template-engine/ecommerce_handlebars/index.js
const express = require("express");
const app = express();
app.get("/", function(req, res) {
res.render("index", { hello: "hola", world: "mundo" });
});
const server = app.listen(3000, function() {
console.log(`Listening http://localhost:${server.address().port}`);
});
Justo antes de las definiciones de rutas agregamos:
const engines = require('consolidate');
app.engine('hbs', engines.handlebars)
app.set('views', './views')
app.set('view engine', 'hbs')
De esta manera podemos crear un nuevo template llamado index.hbs
en la carpeta views
.
Y eso es todo, ya podemos usar nuestros templates hecho con Handlebars.
Para implementar templates con PUG, sólo bastará con instalar PUG con la instrucción npm i -D pug
y definir el uso del view engine respectivo usando los middlewares de manera similar a como vimos antes con Handlebars.
- PUG incluye instrucciones que facilitan el trabajo con las vistas:
- extends: define un template base del que extiende el template actual.
- include: define la inclusión o importación de otro template.
- block: define un placeholder para el contenido de bloques en los que se insertarán partes de un template.
Para más información sobre el uso de PUG, Bulma y FontAwesome, recuerden consultar los enlaces adicionales de esta clase.
Los archivos se encuentran en el folder 📂 platzi-express-creando-nuestro-layout-de-productos-usando-pug/ecommerce/views
🔗 Rama "Manejo de archivos estáticos" en mi repo
const express = require("express");
const path = require("path");
const app = express();
const productsRouter = require('./routes/products');
// SE LE DICE A EXPRESS QUE COMO VA A MANEJAR LOS ARCHIVOS ESTATICOS CON EL PREFIJO /static Y LA DIRECCIÓN DE DONDE ESTA LA CARPETA
// SE CREA LA CARPETA public DENTRO CON LAS CARPETAS assets, images, logo
// SE MODIFICAN LOS ARCHIVOS .pug DENTRO DE LAS CARPETA view PARA QUE IMPORTEN LOS ARCHIVOS STATIC
app.use("/static", express.static(path.join(__dirname, "public")));
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");
app.use("/products", productsRouter);
const server = app.listen(8000, function() {
console.log(`Listening http://localhost:${server.address().port}`);
});
🔗 Anatomía de una API RESTful e implementación CRUD en mi repo
Se modifica el archivo index para implementar la logica para añadir a express la ruta para hacer CRUD
Se crea dentro de la carpeta routes\api\products.js
Acciones en las diferentes tipos de peticiones del CRUD en los endpoints
Forma de las las peticiones al API para ahcer CRUD
Para consumir los datos quemados se crean en la ruta utils\mokcs\products.js
Con Postman en la ruta se hace una peticion GET en la ruta http://localhost:8000/api/products
y devuelve los datos mok y con los mensajes y codigos 200 de OK
const productMocks = [
{
name: "Red shoes",
price: 75,
image: "https://images.unsplash.com/photo-1525966222134-fcfa99b8ae77?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=0e44599932dce6b8440e26fb91e10a69&auto=format&fit=crop&w=800&q=60"
},
{
name: "Black bike",
price: 300,
image: "https://images.unsplash.com/photo-1532298229144-0ec0c57515c7?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=53d820e6622fadd53b8638d60f468ccd&auto=format&fit=crop&w=800&q=60"
}
];
module.exports = productMocks;
Se crea el archivo en la ruta ecommerce/services/products.js
que tiene una clase con la logica de los metodos (GET, POST, PUT, PATCH, DELETE) la cual se encarga de los servicios del negocio, tiene la responsabilidad de llamar a los datos
Se tienen que instalar la libreria 🔗 body-parser para parsear las peticiones en formato JSON request y response en la capa de servicios que actue como middleware y se importa en el archivo principal indexjs
npm i body-parser
🔗 Anatomía de una API RESTful e implementación CRUD EN MI REPO
Capa de servicios modelo vista-controlador MVC
En el archivo routes\api\products.js
(CONTROLADOR) es donde esta lal logica del del negocio, se implementan los metodos de para los request y response y se manejan los errores en las peticiones las cuales estan relacionadas con el archivo routes\products.js
(VISTA) que recibe y envia parametros al controlador
🔗 Destructuring and Function Arguments
🔗 Recapitulando el proyecto EN MI REPO
Se hacen comentarios al proyecto para saber que es lo que realiza en algunas lineas.
Correción de paquete dody-parser se corrige como se importa y se instala el adecuado const bodyParser = require('body-parser');
Para entender mejor la logica de la estructura, se crea la carpeta views
por que ahi es donde esta las vistas, y se mueve de lugar el archivo products.js
dentro ecommerce/routes/views/products.js
.
En en archivo /ecommerce/routes/api/products.js
se insertan console.log
despues de cada peticion para poder saber que es lo que contiene las peticines request y response (req, res)
🔗 POST
Request Object
El objeto req
(Request) en Express representa el llamado HTTP y tiene diferentes propiedades del llamado, como la cadena de texto query (Query params), los parámetros de la URL (URL params), el cuerpo (Body), los encabezados (HTTP headers), etc.
Para acceder al req
basta con acceder al primer parámetro de nuestros router handlers (router middleware) ó middlewares.
Como por ejemplo así lo hemos visto siempre:
app.get('/user/:id', function(req, res) {
res.send('user ' + req.params.id);
});
Pero también funcionaría sin problemas:
app.get('/user/:id', function(request, response) {
response.send('user ' + request.params.id);
});
Exploremos las propiedades más importantes
🚥 req.body
Contiene los pares de llave-valor de los datos enviados el cuerpo body del llamado request. Por defecto es undefined
pero es establecido cuando se usa algún “body-parser” middleware como body-parser y multer.
En Postman cuando hacemos un request y enviamos datos en la pestaña Body, estos middlewares son los que nos ayudan a entender el tipo de datos que vamos a recibir en el req.body
.
Aquí podemos ver como se pueden usar estos middlwares para establecer el valor del req.body
:
const app = require('express')();
const bodyParser = require('body-parser');
const multer = require('multer');
const upload = multer(); // Para datos tipo multipart/form-data
app.use(bodyParser.json()); // Para datos tipo application/json
app.use(bodyParser.urlencoded({ extended: true })); // Para datos tipo application/x-www-form-urlencoded
app.post('/profile', upload.array(), function (req, res, next) {
console.log(req.body);
res.json(req.body);
});
🚥 req.params
Esta propiedad contiene un objeto con las propiedades equivalentes los parámetros nombrados en la ruta. Por ejemplo si tenemos una ruta de la forma /user/:name
entonces la propiedad name
esta disponible como req.params.name
y alli podremos ver su valor, supongamos que llamáramos la ruta con /user/glrodasz
, entonces el valor de req.params.name
seria glrodasz
. Este objeto por defecto tiene el valor de un objeto vacío {}
.
// GET /user/glrodasz
req.params.name
// => "glrodasz"
🚥 req.query
Esta propiedad contiene un objeto con las propiedades equivalentes a las cadenas de texto query
de la ruta. Si no hay ninguna cadena de texto query
tendrá como valor por defecto un objeto vacío {}
.
req.query.q
// => "tobi ferret"
// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
req.query.order
// => "desc"
req.query.shoe.color
// => "blue"
req.query.shoe.type
// => "converse"
MongoDB Atlas es un servicio de bases de datos como servicio que nos permite configurar nuestras bases de datos para entornos de pruebas o producción con diferentes proveedores de nube. Además de todas sus características, Atlas se destaca por ser un servicio mantenido oficialmente por el equipo que desarrolla MongoDB.
mLab también era un servicio de bases de datos con Mongo. Pero fue adquirido por MongoDB a inicios del 2019 para darle más fuerza a MongoDB Atlas y unir fuerzas con el anterior equipo de mLab.
🔗 mongoDB
Usa MongoDB Atlas en lugar de mLab
Para crear tu cuenta y base de datos con MongoDB Atlas solo debes seguir esta clase:
🔗 Creación de una BD en MongoAtlas
Recuerda que los nombres de tus bases de datos y clusters deben ser los mismos en el servicio de Atlas y en tu código.
🔗 Conectando nuestros servicios con mongo DB EN MI REPO
Se tienen que instalar la libreria dotenv para que lear los archivos que comiencen por punto (.) :link: dotenv nom
$ npm i dotenv
Para conectar la app con la BD de mongo se tienen que instalar 🔗 mongodv
$ npm i mongodb
Se crea el archivo para que el/los devs sepan como es el archivo de muestra ecommerce/.env.example
como crear el archivo .env
// CONFIG
PORT=8000
// **MONGODB ATLAS**
DB_USER= USUARIO DE LA BD
DB_PASSWORD= PASSWORD DE LA BD
DB_HOST= ES EL NOMBRE DEL Clusters DESPUES DE LA @ HASTA LA / QUE ESTA ANTES DEL NOMBRE DE LA BD
DB_PORT= ES EL PUERTO QUE USA MONGODB ATLAS QUE PUEDE SER 27017, 27018, 27019
DB_NAME= NOMBRE DE LA BD
// CONFIGURACIÓN PARA CONEXION CON MONGODB ATLAS CON LA LIGA Conect your application
const MONGO_URI = `mongodb+srv://${USER}:${PASSWORD}@${config.dbHost}/${DB_NAME}?retryWrites=true&w=majority`; // prettier-ignore
Se crea el archivo .env
en la raiz del proyecto con con el contenido de ecommerce/.env.example
En el archivo .gitignore
se establece que el archivo .env
git no lo tone en cuenta, agregando .env
Se crea el archivo de configuracion config/index.js
para traer las variables de entorno a la app
El archivo lib/mongo.js
tiene la logica para la conexión a la BD en mongoDB
Para revisar que se conecte a la BD npm run dev
y con Postman en la direccion http://localhost:8000/api/products
hacer la peticion GET para traer los con ayuda de la clase en el archiv lib/mongo.js
y su metodo getAll
para hacer query en la DB
Y como respuesta
{
"data": [],
"message": "products listed"
}
por que por el momento no cuanta con ningun dato.
Se modifico el archivo /services/products.js
la clase ProductsService para que sus metodos llamaran asincronamente a los metodos de la clase MongoLib en /ecommerce/lib/mongo.js
para poder hacer los querys en el forma de mongoDB y se conecte cada vez para poder realizarlo.
Para poder realizar las petiones HTTP por la API con Postman para efectos de este ejemplo se toma en cuenta este documento/fila(BSON)
Revisar para hacer pruebas npm run dev
Se hace POST en http://localhost:8000/api/products
con el body
{
"name": "Butaco blanco",
"price": "1",
"image": "https://images.unsplash.com/photo-1503602642458-232111445657?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=bf884ad570b50659c5fa2dc2cfb20ecf&auto=format&fit=crop&w=800&q=60",
"tags": ["white", "expensive"]
}
Manda como respuesta exitosa y debe de estar registrado en mondoDB Atlas.
El valor de "data"
es el valor del _id
que le asigna mongoDB de forma automatica
{
"data": "5eea63bf96673e442488a92d",
"message": "product created"
}
Se hace GET en http://localhost:8000/api/products
para comprobar los datos guardados
Se hace GET en http://localhost:8000/api/products/5eea63bf96673e442488a92d
traer el producto especifico por id
Se hace GET en http://localhost:8000/api/products/?tags[]=white
para buscar en el arreglo de tags que se encuentra en cada producto y se pude realizar la busqueda por varios tags http://localhost:8000/api/products/?tags[]=white&tags[]expensive
Para actualizar PUT el producto se hace por id
en la direccion http://localhost:8000/api/products/5eea63bf96673e442488a92d
con el body json a actualizar.
{
"name": "Black Bike",
"price": "300",
"image": "https://images.unsplash.com/photo-1503602642458-232111445657?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=bf884ad570b50659c5fa2dc2cfb20ecf&auto=format&fit=crop&w=800&q=60",
"tags": ["expensive", "black"]
}
Y en caso que sea exitosa la actualización regresa
{
"data": "5eea63bf96673e442488a92d",
"message": "product updated"
}
Para eliminar DELETE el producto se hace por id
en la direccion http://localhost:8000/api/products/5eea63bf96673e442488a92d
y regresa el mensaje que se elimino correctamente.
{
"data": "5eea63bf96673e442488a92d",
"message": "product deleted"
}
El middleware es una funcion con 3 argumentos, como una serie de capas que por lo regular es para validaciones
- Tipod de middleware
- 3rd party
- Router level
- Application level
- Built-in
- Error-handling
🔗 19. Manejo de errores usando un middleware EN MI REPO
Se crea una carpeta "middlewares" con el archivo "errorsHandlers.js" -> ecommerce/utils/middlewares/errorsHandlers.js
Despues de tiene que instalar en la app en el archivo index.js
y deben de ir al final de todas las rutas.
const {
logErrors,
clientErrorHandler,
errorHandler
} = require("./utils/middlewares/errorsHandlers");
/* ... */
// error handlers
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);
Se tiene que crear despues una vista para los errores creando ecommerce/views/error.pug
para mostrar los errores en vista.
Para comprovar su funcionameitno se puede hacer provocando un error en /ecommerce/routes/views/products.js
dentro del try
throw new Error('This is a new error');
Y tambien se muestra en consola el stack.
Evitar datos que manden datos que no corresponden a esquemas que no corresponden.
La funcio que devuelve la funcion es el middleware.
Para lograrlo se crea ecommerce/utils/middlewares/validationHandler.js
la cual la libreria Joi se encargara de realizar la validación.
Hcer una cuenta en 🔗 Sentry
Al ingresar al dashboard de Sentry lo que podemos hacer es ir directamente onboarding haciendo click en la alerta amarilla que aparece en la parte superior o mediante el link https://sentry.io/onboarding/<organización>/
Allí nos pedirá que seleccionemos un lenguage y un team, para el lenguaje seleccionaremos Server > Node.js
le daremos un nombre al proyecto dejaremos el team que tiene por defecto y a continuación haremos click en Create Project
.
Después de ello nos entregara unas instrucciones para configurar Sentry. Lo que haremos es instalar la dependencia de Sentry e inicializar su configuración en el archivo /utils/middlewares/errorsHandlers.js
Recordemos que no debemos dejar ninguna key, secret o identificador quemado en nuestro código por lo que la recomendación es moverlo a variables de entorno y configuración usando el archivo
.env
// .env.example y .env
SENTRY_DNS=
SENTRY_ID=
// config/index.js
require("dotenv").config();
const config = {
sentryDns: process.env.SENTRY_DNS,
sentryId: process.env.SENTRY_ID
};
module.exports = { config: config };
// utils/middlewares/errorsHandlers.js
const Sentry = require("@sentry/node");
const { config } = require("../../config");
Sentry.init({ dsn: `https://${config.sentryDns}@sentry.io/${config.sentryId}` });
Y dentro de nuestro middleware logErrors agregamos el capturador de errores de Sentry:
// utils/middlewares/errorsHandlers.js
function logErrors(err, req, res, next) {
Sentry.captureException(err);
console.error(err.stack);
next(err);
}
Le damos click en All done! y nos reenviara al dashboard dónde podremos monitorear los errores que ocurren en nuestra aplicación.
De esta manera cualquier error que ocurra en nuestra aplicación desplegada en producción estará registrado en Sentry y podremos revisarlos y corregirlos proactivamente.
Si quieres obtener más información sobre cómo capturar eventos y mensajes manualmente puedes consultarlo aquí https://docs.sentry.io/error-reporting/capturing/?platform=node
/ecommerce/.env
// CONFIG
PORT=8000
// **MONGODB ATLAS**
DB_USER=
DB_PASSWORD=
DB_HOST=
DB_PORT=
DB_NAME=
SENTRY_DNS=
SENTRY_ID=
/ecommerce/config/index.js
// ARCHIVO DE CONFIGURACION PARA TRAER LAS VARIABLES DE ENTORNO A LA APP
require("dotenv").config();
const config = {
// VARIABLE QUE INDICA SI ESTA EN PRODUCCION O DESARROLLO
dev: process.env.NODE_ENV !== "production",
port: process.env.PORT,
dbUser: process.env.DB_USER,
dbPassword: process.env.DB_PASSWORD,
dbHost: process.env.DB_HOST,
dbPort: process.env.DB_PORT,
dbName: process.env.DB_NAME,
sentryDns: process.env.SENTRY_DNS,
sentryId: process.env.SENTRY_ID
};
module.exports = { config };
ecommerce/utils/middlewares/errorsHandlers.js
// import * as Sentry from '@sentry/node';
// or using CommonJS
const Sentry = require('@sentry/node');
const { config } = require("../../config");
Sentry.init({ dsn: `https://${config.sentryDns}@o408997.ingest.sentry.io/${config.sentryId}` });
// MIDDLEWARE PARA HACER LOG DE LOS ERRORES
function logErrors(err, req, res, next) {
Sentry.captureException(err); // AGREGAMOS EL CAPTURADOR DE ERRORES DE Sentry
console.log(err.stack);
next(err); // SE LLAMA AL RPOXIMO MIDDLEWARE PASANDO LE EL ERROR CON EL CALLBACK next
}
- Joi: Object Schema Valitation
- Boom: HTTP-friendly error objects; enviar errores de forma mas agradable.
- 🔗 Unacceptable Browser HTTP Accept Headers (Yes, You Safari and Internet Explorer)
- 🔗 @hapi/joi
- 🔗
hapijs/joi
$ npm install @hapi/joi
Se crea ecommerce/utils/schemas/products.js
el cual tiene la logica para poder establecer los tipos de datos que se requieren y cuales son requeridos con la ayuda de la libreria @hapi/joi
Despues en utils/middlewares/validationHandler.js
se importa la libreria @hapi/joi
para poder exportarla y que funcione como un middleware para validar los tipos de datos por medio de la funcion validationHandler
la cual manda a llamar a otra funcion por medio de scope para ocupar la libreria joi y validar el schema
y la data
que el json que se pretende validar para insertar datos.
En el archivo /routes/api/products.js
se importa el schema
y el middleware para poder validar al momento de insertar y actualizar en la peticion POST y PUT con la logica de validación de @hapi/joi
// ACTUA COMO MIDDLEWARE ANTES DE QUE SE EJECUTE LA PETICION POST
router.post("/", validation(createProductSchema), async function(req, res, next)
Se instala Boon como dependencia
$ npm install @hapi/boom
Y se importa en /utils/middlewares/errorsHandlers.js
la lógica de los middlewares es que el error pasa por las capas de manejador de errores
- withErrorStack ⬇️
- logErrors ⬇️
- wrapErrors ⬇️
- clientErrorHandler ⬇️
- errorHandler ⬇️
Despues en /utils/middlewares/validationHandler.js
se implenta Boom para retornar error 400 de pagina no encontrada
Se crea el archivo ecommerce/utils/isRequestAjaxOrApi.js
para realizar dos validaciones, si el request
es de una llamada para vista "html" es una llamada por AJAX(API)
Al final se tiene que mandar a llmar y usar el middleware wrapErrors
en /ecommerce/index.js
Justo despues de todas las rutas y antes de los middlewares de validación se especifica que se si no encuentra la para html o API se muestre la pagina con el error 404 con ayuda de Boom
// MIDDLEWARE PARAMOSTRAR LA PAGINA 404 AL FINAL DE TODAS LAS RUTAS
// SI NO CAE EN NINGUNA RUTA ENTONCES SE MUESTRA LA PAGINA 404
app.use(function (req, res, next) {
if (isRequestAjaxOrApi(req)) {
const {
output: { statusCode, payload },
} = boom.notFound();
res.status(statusCode).json(payload);
}
res.status(404).render("404");
});
Al hacer peticion a una "vista" que no existe se muestra la paguina 404 y cuando se hace una peticion AJAX a API a una dirección que no existe con X-Requested-With=XMLHttpRequest
en Postman responde
{
"statusCode": 404,
"error": "Not Found",
"message": "Not Found"
}
- Middlewares populares de express:
express.json
obody-parser.json
: Sirve para que nuestras requests entiendan jsoncors
: Middleware de seguridad que sirve para que los requests sean aceptados de ciertos dominios específicosmorgan
: Middleware para el manejo de logshelmet
: Middleware de seguridadexpress-debug
: El debugger predeterminado de express para debuggear tu aplicaciónexpress-slash
: Permite aceptar urls sin el slash (’/’)passport
: Para autentifación y autorización |
-
🔗 [JWT])(https://jwt.io/)
-
JWT su estructura es:
- Header
- Payload
- Signature
JSON WEB TOKENS tiene la estructura
Instalar 🔗 passport
$ npm i passport
Instalar 🔗 passport-http
$ npm i passport-http
Instalar 🔗 passport-jwt
$ npm i passport-jwt
Instalar 🔗 jsonwebtoken
$ npm i jsonwebtoken
Instalar 🔗 bcrypt
$ npm i bcrypt
Instalar 🔗 chalk
$ npm i chalk
$ npm i passport passport-http passport-jwt jsonwebtoken bcrypt chalk
Se crean nuevas variables en /ecommerce/.env.example
// AUTH
AUTH_ADMIN_USERNAME=
AUTH_ADMIN_PASSWORD=
AUTH_ADMIN_EMAIL=
AUTH_JWT_SECRET=
Se crea el archivo /ecommerce/scripts/mongo/seed-admin.js
el cual tiene como finalidad la administración del usuario admin con la bd de mongoDB ocupando la libreria bcrypt para implementar hasheado de las credenciales
Ejecutar el archivo para crear el usuario admin si no esta creado.
$ node scripts/mongo/seed-admin.js
En ExpressJS/ecommerce/utils/auth/strategies/basic.js
se crea la estrategia básica para cuando un usuario admin haga login sea autentificado, si esiste en la bd se regresa el usuario, caso cintrario se regresa el error de que le usuario no esta autentificado.
Se crea /ecommerce/routes/api/auth.js
para realizar un end point en API para que el usuario admin haga login y se obtiene token de autentificación.
Despues el endpoint se tiene que instalar en el index.js
const authApiRouter = require("./routes/api/auth");
/* ... */
app.use("/api/auth", authApiRouter);
Para verificar su funcionamiento con Postman en el endpoint http://localhost:8000/api/auth/token
se hace POST con el usuario y contraseña del usuario admin con el tipo de autentificación basica y como respuesta se obtiene el token de autentificación correcta
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYWNrbmlsYW4iLCJlbWFpbCI6Im1hY2tAbWFpbC5jb20iLCJpYXQiOjE1OTI1MjkxMzgsImV4cCI6MTU5MjUzMDAzOH0.FNGk24hHHKvNNlO9iDz6gJwljTAStUbJWh-1t_o9nZ8"
}
En el archivo /utils/auth/strategies/jwt.js
tiene la lógica para asegurar que las ritas sean solo usadas por un usuario autorizado, utlizando la estrategia jsonwebtoken
La cual se tiene que inluir en /routes/api/products.js
para poder validar las rutas de actualización y de borrado y que solo los usuario autorizados tengan acceso a ejecutar estas peticiones HTTP
Para verificar su funcionamiento con Postman en el endpoint http://localhost:8000/api/products/5eea6d9796673e442488a92e
se hace PUT con el ID de un producto con el tipo de autentificación Bare Token
y con el token de autorización puesto en Postman y en el body el JSON a actualizar
{
"name": "Butaco blanco",
"price": "2",
"image": "https://images.unsplash.com/photo-1503602642458-232111445657?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=bf884ad570b50659c5fa2dc2cfb20ecf&auto=format&fit=crop&w=800&q=60",
"tags": ["blue", "expensive"]
}
y como respuesta
{
"data": "5eea6d9796673e442488a92e",
"message": "product updated"
}