Muy entendible todo. La verdad me encanta este curso hasta ahora.
Introducción
¿Qué es Express.js?
Configuración del entorno de desarrollo para este curso
Instalación de Express.js y tu primer servidor HTTP
Routing con Express.js
CRUD
¿Qué es una RESTful API?
¡Es tu turno: crea un tutorial!
GET: recibir parámetros
GET: parámetros query
Separación de responsabilidades con express.Router
Instalación de Postman o Insomia
POST: método para crear
PUT, PATCH y DELETE
Códigos de estado o HTTP response status codes
Servicios
Introducción a servicios: crea tu primer servicio
Crear, editar y eliminar
Async await y captura de errores
Middlewares
¿Qué son los Middlewares?
Middleware para HttpErrors
Manejo de errores con Boom
Validación de datos con Joi
Probando nuestros endpoints
Middlewares populares en Express.js
Deployment
Consideraciones para producción
Problema de CORS
Deployment a Heroku
Deployment a Vercel
Próximos pasos
Continúa en el Curso de Node.js con PostgreSQL
You don't have access to this class
Keep learning! Join and start boosting your career
The use of validation middleware is fundamental to ensure data integrity and consistency in our applications. We have already created the entire structure of our middleware using Joy. Now, it is time to see how we apply them to each of our endpoints in an efficient way.
First, it is crucial to remember that we have created a function called ValidatorHandler
. This is essentially a function that returns another function (thanks to JavaScript closures) and allows the creation of middleware dynamically. When using ValidatorHandler
, we send a schema and specify the property containing the information needed to validate the data.
The first thing we need to do is to import our ValidatorHandler
function and the necessary schemas into our routes. This is essential since the routes are the ones that handle the request and response, and it is here where we will configure which schema to use for each endpoint.
const {ValidatorHandler} = require('middlewares');const {createProductSchema, updateProductSchema, getProductSchema} = require('schemas');
Each endpoint must run and define its own schema. The middleware will run on the route, accompanying other middleware or processes that each endpoint has configured. This ensures that validation is performed just before the service is executed.
For example, in an endpoint that receives an ID as a parameter, we must be sure that this ID complies with the expected format. This is how we apply it:
router.get('/products/:id', ValidatorHandler(getProductSchema, 'params'), (req, res) => { res.json({ message: 'Product successfully obtained' }); }) );
In some cases, a route needs multiple validations. For example, an endpoint that requires validating both body and URL parameters. Middlewares are executed sequentially, so we can chain them together to verify each part of the data:
router.patch('/products/:id', ValidatorHandler(getProductSchema, 'params'), ValidatorHandler(updateProductSchema, 'body'), (req, res) => { res.json({ message: 'Product successfully updated' }); }) );
When a validation error occurs, it is important to provide a clear and specific response. Joy detects errors and returns an HTTP code 400 (Bad Request) if the validation fails. Initially, Joy only returns the first error it encounters. However, we can configure Joy to report all errors at once using the abortEarly: false
option.
const options = { abortEarly: false };return schema.validate(data, options);
With this setting, the user receives all validation errors at once, improving the developer and end-user experience.
Field specificity: Clearly define mandatory fields and their types, such as a string or number.
Value constraints: Set limits or rules, such as minimum and maximum length, or specific data type (e.g., alphanumeric).
Field requirement: Determines if a field is mandatory using required
.
Valid URLs: Defines a field as a URL, if required.
This approach not only optimizes data integrity when interacting with our services, but also improves code structure and maintainability. By integrating validation middleware in each path, we ensure that invalid requests never reach the services in production, thus improving the robustness of our system.
Contributions 51
Questions 22
Muy entendible todo. La verdad me encanta este curso hasta ahora.
Si te preguntas cuántas funciones middleware puedes enviar como callback, la respuesta es: las que quieras. Esto siempre y cuando las separes con coma. Las puedes llamar si las definiste fuera, ejecutar o incluso llamar un array de funciones middlewares.
Ejemplo de la documentación Express
const cb0 = function (req, res, next) {
console.log('CB0')
next()
}
const cb1 = function (req, res, next) {
console.log('CB1')
next()
}
app.get('/example/d', [cb0, cb1], function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from D!')
})
Les comparto una forma de personalizar los mensajes de la validación en el productSchema.js Ejemplo si queremos los mensajes en español
const name = joi.string().min(3).max(15).messages({
'string.base': `" nombre "debe ser un tipo de 'texto'`,
'string.empty': `"nombre "no puede ser un campo vacío`,
'string.min': `"nombre" debe tener una longitud mínima de {#limit}`,
'string.max': `"nombre" debe tener una longitud máxima de {#limit}`
});
resultado
Si alguno ve este error en la consola:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Lo puede solucionar anteponiendo un return
antes de los res
. Esto sucede porque tenemos varios middlewares en los que se responde al cliente, y no se corta la ejecución de los mismos luego de enviado el primer res
, por eso el error.
Quedaría así, (ejemplo para boomErrorHandler):
function boomErrorHandler(err, req, res, next) {
if (err.isBoom) {
const { output } = err;
return res.status(output.statusCode).json(output.payload);
}
next(err);
}
Me ha gustado mucho el curso, el profe explica super claro todos los conceptos y practicar ayuda mucho a entender mejor lo conceptos, he estado desarrollando lo que hacemos para productos con los usuarios.
Asi sería mi Schema para los usuarios:
const Joi = require('joi');
const id = Joi.string().uuid();
const name = Joi.string().alphanum().min(3).max(15);
const lastname = Joi.string().alphanum().min(3).max(15);
const email = Joi.string().email();
const createUserSchema = Joi.object({
name : name.required(),
lastname : lastname.required(),
email: email.required(),
});
const getUserSchema = Joi.object({
id: id.required(),
});
module.exports = {createUserSchema, getUserSchema};
Y asi las ruta de usuarios:
const express = require("express");
const UserService = require("../services/userService");
const validatorHandler = require("../middlewares/validatorHandler");
const {createUserSchema, getUserSchema} = require("../schemas/userSchema");
const router = express.Router();
const service = new UserService();
router.get("/", async (req, res) =>{
const users = await service.find();
res.json(users);
});
router.post("/",
validatorHandler(createUserSchema, "body"),
async (req, res) =>{
const body = req.body;
const newUser = await service.create(body);
res.status(201).json(newUser);
}
);
router.delete("/:id", async (req, res) =>{
const {id} = req.params;
const answer = await service.delete(id);
res.status(200).json(answer);
});
module.exports = router;
Consejo: pongan en las validaciones de price: .strict() ya que sino aceptará strings con números como “1000” y eso puede generarnos problemas después.
Este curso es increíble, hace la diferencia un buen profesor. Me han quedado claro conceptos que no había entendido bien en cursos anteriores.
Para quien tenga dudas con el código de porque el profe. puso, en validatorHandler.js
const data = req[property];
Se debe a que para obtener data dentro de un objeto se hace de dos formas.
const objeto = {
name: "victor",
number: 123
}
console.log(objeto.number)
const objeto = {
name: "victor",
number: 123
}
console.log(objeto["number"])
Profe, tome este curso porque en mi emprendimiento ya era necesario crear una API.
El curso es muuuuuuuuuuy bueno, has tomado la información relevante con clases cortas y concisas. De Verdad muchas gracias! me ha servido TODO
Muy buen curso, hasta el momento he entendido todo .
El tema de los middlewares lo habia escuchado pero recien ahora lo estoy empezando a entender.
Sin duda uno de los mejores cursos que me he encontrado en Platzi. Gracias Platzi, Gracias Nicolas Molina eres la mera ley… =)
Para realizar ciertas restricciones con joi y poder requerir al menos un valor por ejemplo que para actualizar un usuario, por lo menos en el body venga un valor pueden usar la siguiente estructura:
por si les llega a pasar
puse: con una coma al final y me falló la api
{
"name": "Camisa",
}
lo puse así: y funcionó correctamente
{
"name": "Camisa"
}
para que no acepte precios en formato texto ejemplo “price”: “1000” al validar hay que desactivar la opción por defecto que lo convierte a int.
const {error} = schema.validate(data,{convert: false});
Si quieren validar que el name sea requerido pero si este contiene espacios en blanco, solamente es omitir el alphanum().
const name = Joi.string().min(3).max(15);
Mi solución como reemplazo del alphanum:
const name = Joi.string().pattern(/^[a-zA-Z0-9 ]+$/).min(3).max(25);
De esta forma se valida que el contenido sea números o letras tanto en mayúscula como minúscula.
De la documentación de Joi:
number
Generates a schema object that matches a number data type (as well as strings that can be converted to numbers).
Por si alguien también se percató que se está aceptando price como number o string.
En el archivo
product.schema.js
Agregue .strict() para que al momento de hacer PATCH no pueda recibir, me di cuenta que “price” puede recibir numeros en una cadena de strings
{
"price": "2"
}
.
const price = Joi.number().integer().min(10).strict();
Ahora entiendo a las personas que dicen las validaciones deben ir en el back end
hasta ahora este curso de backend con nodejs me saco el sombrero, muy buena didactica en la enseñanza y comprension de la logica a compraracion de react, wepack, nextjs que si tenia muchos vacios y muchos errores
¿Por qué el profesor no hace esto?:
const updateProductSchema = Joi.object({
name,
price,
image
})
está colocando los ‘:’ y en JS eso es innecesario si la clave tiene el mismo nombre que el valor
Está muy claro y práctico todo, genial el profe
Para los que no les funcione en el update y hagan la petición de que los campos son requeridos tienen que poner optional
const updateCategorySchema = Joi.object({
name : name.optional(),
image : image.optional(),
});
Otra forma de aplicar middlewares es
router.use('/:id', validatorHandler(getProductSchema, "params"))
Esto aplica el middleware a la ruta ‘/’ que le pasan por parametro el id
Áma veo middleware por todos lados.
products.route.js
:
const express = require('express');
const ProductsService = require('../../services/product.service');
const validatorHandler = require('../../middlewares/validator.handler');
const {
createProductSchema,
updateProductSchema,
getProductSchema,
deleteProductSchema,
} = require('../../schemas/product.schema');
const router = express.Router();
const service = new ProductsService();
router.get('/', async (req, res) => {
const products = await service.find();
res.status(200).json(products);
});
router.get(
'/:id',
validatorHandler(getProductSchema, 'params'),
async (req, res, next) => {
try {
const { id } = req.params;
const product = await service.findOne(id);
if (product) {
res.status(200).json(product);
} else {
res.status(404).json({ message: 'Product not found' });
}
} catch (error) {
next(error);
}
}
);
router.post(
'/',
validatorHandler(createProductSchema, 'body'),
async (req, res) => {
const body = req.body;
const newProduct = await service.create(body);
res.status(201).json({
message: 'created',
data: newProduct,
});
}
);
router.patch(
'/:id',
validatorHandler(getProductSchema, 'params'),
validatorHandler(updateProductSchema, 'body'),
async (req, res, next) => {
try {
const { id } = req.params;
const body = req.body;
const product = await service.update(id, body);
res.status(200).json({
message: 'updated',
product,
});
} catch (error) {
next(error);
}
}
);
router.delete(
'/:id',
validatorHandler(getProductSchema, 'params'),
async (req, res) => {
const { id } = req.params;
const rta = await service.delete(id);
res.status(200).json({
message: 'deleted',
rta,
});
}
);
module.exports = router;
Para el error en validatorHandler.js
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Debemos agregar el next(); después del if ya que sino hay error debe continuar al siguiente middleware y aparentemente en el video donde lo contruimos no aparece. Pero, si aparece en los recursos de este video.
const boom = require('@hapi/boom');
function validatorHandler(schema, property) {
return (req, res, next) => {
const data = req[property];
const { error } = schema.validate(data, { abortEarly: false });
if (error) {
next(boom.badRequest(error));
}
next(); // __IMPORTANTE
}
}
module.exports = validatorHandler;
Es la segunda vez que veo el curso y ahora lo veo mucho más claro todo.
la mayoria de javascript ha estado escrita con el patron de diseño builder pattern, es donde siempre se retorna this, y permite hacer el codigo tan hermoso como el siguiente:
const name = Joi.string().alphanum().min(3).max(10);
Para las personas que no obtengan el error deseado, pueden revisar el orden en el que se ejecutan los middlewares de forma global en su archivo (index) o en su (app), si los tienen separados
Así es como defini mis esquemas de validación
Así es como definí mi validado:
Así es como apliqué las validaciones en el controlador/router
Así se ven mis resultados probados en postman
📑 Creo que una forma de manejar los valores a verificar es por un objeto y desestructuración, ya que permite actualizar la plantilla sin tener que repetir los valores no requeridos.
const Joi = require("joi");
//👇️
//--Plantilla de producto--
const defaultProductSchema = {
id: Joi.string().uuid(),
name: Joi.string().alphanum().min(2).max(20),
price: Joi.number().integer().min(100),
image: Joi.string().uri(),
}
//-------
//todos los datos requeridos (id no generado por el cliente si no por la API)
const createProductSchema = Joi.object({
name: defaultProductSchema.name.required(),
price: defaultProductSchema.price.required(),
image: defaultProductSchema.image.required(),
})
//solo verificacion de ID en params
const findProductSchema = Joi.object({
id: defaultProductSchema.id.required()
})
/*ID requerido pero sin embargo verifica todas las propiedades para verificar
cualquier cambio en cualquier propiedad*/
const updatedProductSchema = Joi.object({
...defaultProductSchema,
id: defaultProductSchema.id.required()
})
const deleteProductSchema = Joi.object({
id: defaultProductSchema.id.required()
})
module.exports = { createProductSchema, findProductSchema, updatedProductSchema, deleteProductSchema };
Este curso es genial
Connect validation middleware (Joi schema) with router
He implementado el validador en Typescript de la siguiente manera:
//validator.handler.ts
import { Request, Response } from 'express';
import boom from '@hapi/boom';
import Joi from "joi";
/** Joi validator middleware */
export function validatorHandler(schema: Joi.Schema, property: string) {
return (req: Request, res: Response, next: (arg0?: Error) => void) => {
const data = (<any>req)[property];
const { error } = schema.validate(data);
if(error) {
next(boom.badRequest(error.message));
}
next();
}
}
A las rutas les pasamos los validadores que deseamos antes de que entre a la lógica del producto en si. En el caso de post, tenemos un validador que importamos del schema, y un body (que es de donde sacaremos la información para validar). Nuestro validador es para crear un producto. Entonces tenemos: validadorHandler( tipoDeValidador, ‘dondeLoSacamos’). Luego continua la lógica.
Products.js
router.post('/',
validatorHandler(createProductSchema, 'body'),
async (req, res) => {
const body = req.body;
const newProduct = await service.create(body);
res.status(201).json(newProduct);
});
En caso del patch tenemos 2 parametros y requerimos 2 validadores. Nuestro primer validador es de obtener el parámetro (ya que no requerimos realizar cambios a todo el body de nuestro producto, esa es la característica de patch). Y por otro lado, para realizar cambios en esos parametros (que serán inscriptos en el body)
router.patch('/:id',
validatorHandler(getProductSchema, 'params'),
validatorHandler(uptdateProductSchema, 'body'),
async (req, res) => {
try {
const { id } = req.params;
const body = req.body;
const product = await service.update(id, body)
res.json(product);
} catch (error) {
res.status(404).json({
message: error.message
})
}
});
Validator.handler.js
function validatorHandler (schema, property){
return (req, res, next) => {
const data = req[property];
la propiedad ‘abortEarly: false’ nos permite determinar si enviar el mensaje de error al primer error que encontremos. Por default es verdadero.
const { error } = schema.validate(data, { abortEarly: false });
if (error){
next(boom.badRequest(error));
}
next();
}
}
Muy bien, ya puedo validar los datos!
Excelente el curso, todo muy claro y entendible.
Ahora si me quedo clarísimo como funciona el middleware, que bien que explica, cada detalle
Un curso increíble. Estoy entendiendo todo a detalle.
la verdad excelente, boom y joi de maravilla conjunto los middlewares es una hermosura utilizarlo 😄
Si quieren realizar validaciones mas complejas que las que trae Joi por defecto pueden usar regex, yo lo usé de la siguiente forma:
const Joi = require('joi');
const id = Joi.string().uuid();
const email = Joi.string().email();
const firstName = Joi
.string()
.min(2)
.max(50)
.regex(/^\w+(?:\s+\w+)*$/)
.messages({
"string.pattern.base": "First Name accepts alphabetic characters, numbers and spaces"
});
const lastName = Joi
.string()
.min(2)
.max(50)
.regex(/^\w+(?:\s+\w+)*$/)
.messages({
"string.pattern.base": "Last Name accepts alphabetic characters, numbers and spaces"
});
const photo = Joi.string().uri();
const getUserSchema = Joi.object({
id: id.required()
});
const createUserSchema = Joi.object({
email: email.required(),
firstName: firstName.required(),
lastName: lastName.required(),
photo: photo
});
const updateUserSchema = Joi.object({
firstName: firstName,
lastName: lastName,
photo: photo
});
module.exports = { getUserSchema, createUserSchema, updateUserSchema };
Want to see more contributions, questions and answers from the community?