Les dejo mis apuntes de la clase 🙈
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
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
No se trata de lo que quieres comprar, sino de quién quieres ser. Invierte en tu educación con el precio especial
Antes: $249
Paga en 4 cuotas sin intereses
Termina en:
Nicolas Molina
Aportes 69
Preguntas 18
Les dejo mis apuntes de la clase 🙈
Así entendí “clean arquitecture”.
Me encantaría leer correcciones.
Uff hasta ahora el curso me ha encantado, super claro y bien estructurado. Asi va mi codigo por ahora, me ha parecido muy buena la parte de encapsular el funcionamiento en clases 😄
Products.routes
const express = require('express');
const router = express.Router();
const productService = require('../services/product.service');
const product = new productService();
router.get('/', (req, res) => {
let {limit, offset} = req.query;
res.json(product.find({offset, limit}));
});
router.get('/:id', (req, res) => {
const {id} = req.params;
res.json(product.findOne(id));
});
router.post("/", (req, res) => {
const {name, price, image} = req.body;
const newProduct = product.create({name, price, image});
if (newProduct) {
res.status(201).json({
message: "Product added",
data: newProduct
});
} else
res.status(501).json({message: "internal error"});
});
router.patch("/:id", ((req, res) => {
const id = parseInt(req.params.id);
const {name, price, image} = req.body;
const updateProduct = product.update(id, {name, price, image});
if (updateProduct) {
res.json({
message: "Product updated",
data: req.body
});
} else {
res.status(501).json({message: "Internal error"})
}
}));
router.delete("/:id", (req, res) => {
const id = parseInt(req.params.id);
const currentProduct = product.delete(id);
if (currentProduct) {
res.status(201).json({message: "Product deleted", data: currentProduct});
} else {
res.status(404).json({message: "Product not found"});
}
});
module.exports = router;
prduct.service.js
const faker = require("faker");
class ProductService {
constructor() {
this.products = [];
this.faker();
}
faker(quantity = 100) {
for (let i = 0; i < quantity; i++) {
this.products.push({
id: i + 1,
name: faker.commerce.productName(),
price: parseInt(faker.commerce.price(), 10),
image: faker.image.imageUrl()
});
}
}
create(product = {}) {
const newProduct = {
id: this.products[this.products.length - 1].id + 1,
name: product.name,
price: parseInt(product.price),
image: product.image
}
this.products.push(newProduct);
return newProduct;
}
find(filter = {}) {
return this.products.slice(filter.offset | 0, filter.limit ? parseInt(filter.limit) + parseInt((filter.offset | 0)) : undefined)
}
findOne(id) {
return this.products.find((p) => p.id = id);
}
update(id, changes = {}) {
const product = this.products.find((prod) => prod.id === id);
const idx = this.products.findIndex((prod) => prod.id === id);
if (product) {
this.products[idx] = {
id: product.id,
name: changes.name || product.name,
price: changes.price || product.price,
image: changes.image || product.image
};
return product;
}
}
delete(id) {
const product = {...this.products.find((prod) => prod)};
if (product) {
this.products = this.products.filter((p) => p.id !== id);
return product;
}
}
}
module.exports = ProductService;
Actualmente el módulo random de @faker-js/faker está @deprecated, y se recomienda usar en su lugar:
faker.string.uuid()
para generar un id.
Resumen de la clase:
Concepto
Los servicios es donde encapsulamos todos los casos de usos y comenzar a interactuar con la lógica de negocio.
En el caso de una tienda: hacer compras, transacciones, etc.
.
Estructura
Esta arquitectura está definida por capas.
Entidades:
Casos de uso
Controladores
.
Flujo de trabajo:
Me está encantando este curso, cómo es que lo explica el profesor.
Incluso me estoy interesando mucho por el mundo del backend. 😁
Para la peticion get que busca por id una manera de validar primero si hay algun valor retornado se puede hacer de la siguiente manera:
router.get('/:id', (req, res) => {
const { id } = req.params;
const product = service.findOne(id);
if(!product){
res.status(404).json({
message: `El producto con id ${id} no existe`
});
} else {
res.status(200).json({
product
});
}
});
De esta manera nos aseguramos que traiga valor la consulta de lo contraria manejamos la respuesta que no encontró el objeto buscado.
👀 OJO:
Ya que el profesor menciona mucho el tema de las convenciones a la hora de nombrar las cosas, aqui les dejo esta convención:
Tomen nota ✍️😉
Este profesor es muy bueno, el tema de los servicios siempre me habían confundido cundo los usábamos en otros cursos de la Escuela de JS.
De una vez agregue una validación si no encuentra el id para retornar un 404 (yo lo estoy haciendo con books en vez de products):
router.get('/:id', (req, res) => {
const { id } = req.params;
const book = service.findOne(id);
if ( book ) {
res.status(200).json({
message: "Libro encontrado",
book
})
} else {
res.status(404).json({
message: "Libro no encontrado :( ",
})
}
})
Hasta este punto me siento super bien, con la forma de explicar de este profe, grande Nico!
Les comparto mi código con mejoras para realizar un limit
Felicitationes Nicolas, excelente profesor.
Excelente, ahora entiendo cuándo se refieren a servicios.
mas profesores así en Platzi.
Servicios Web
Nacen de la necesidad de cubrir la lógica de negocio.
Lógica de negocio
El término lógica de negocio hace referencia a la parte de un sistema que se encarga de codificar las reglas de negocio del mundo real que determinan cómo la información puede ser creada, almacenada y cambiada.
Servicios
Los servicios es donde encapsulamos todos los casos de usos y comenzamos a interactuar con la lógica de negocio.
Los servicios deben deben ser construidos por una arquitectura, que en este caso será:
Clean Architecture
Clean architecture es un conjunto de principios cuya finalidad principal es ocultar los detalles de implementación a la lógica de dominio de la aplicación.
De esta manera mantenemos aislada la lógica, consiguiendo tener una lógica mucho más mantenible y escalable en el tiempo.
Capas
-Entidades:
En esta capa encontramos las entidades base del negocio.
En nuestro caso: productos, categorías, órdenes de compra.
-Casos de uso
En esta capa tenemos lo relacionado a la lógica de negocio
En esta capa se encuentra los servicios
-Controladores
En esta capa se brinda el acceso.
Aquí encontramos el routing.
Flujo de trabajo:
Implementando el query limit a products.
.
products.service
const faker = require('faker');
class ProductsService {
constructor() {
this.products = [];
const limit = 1000;
this.generate(limit);
}
generate(limit) {
for (let index = 0; index < limit; index++) {
this.products.push({
id: faker.datatype.uuid(),
name: faker.commerce.productName(),
price: parseInt(faker.commerce.price(), 10),
image: faker.image.imageUrl(),
});
}
}
create() {}
find(limit) {
return this.products.slice(0, limit);
}
findOne(id) {
return this.products.find((item) => item.id === id);
}
update() {}
delete() {}
}
module.exports = ProductsService;
products.router
const express = require('express');
const ProductsService = require('./../services/products.service');
const router = express.Router();
const service = new ProductsService();
router.get('/', (req, res) => {
const { limit } = req.query;
const products = service.find(limit || 10);
res.json({
size: products.length,
products,
});
});
router.get('/filter', (req, res) => {
res.send('Yo soy un filter');
});
router.get('/:id', (req, res) => {
const { id } = req.params;
const product = service.findOne(id);
if (!product) {
res.status(404).json({
message: `El producto con id '${id}' no existe`,
});
} else {
res.status(200).json(product);
}
});
.
Sin limit
Con limit
No olviden colocar el new cuando instancian service. Estuve 30 minutos buscando el error, hasta que tuve que chequear el cod del profesor.
Y si se olvidan que se instancia con new, no se olviden de leer la terminal; estaba tan ansioso en buscar el error que identificaba el donde, y no vi que literalmente la terminal me decia:
const service = ProductService();
^
TypeError: Class constructor ProductService cannot be invoked without 'new'
Wowww, este curso me ha hecho amar el backend, gracias!
La forma de importar las funcionalidedes de @faker-js/faker, al día de hoy son:
export function createRandomUser(): User {
return {
userId: faker.string.uuid(),
username: faker.internet.userName(),
email: faker.internet.email(),
avatar: faker.image.avatar(),
password: faker.internet.password(),
birthdate: faker.date.birthdate(),
registeredAt: faker.date.past(),
};
}
Otra forma muy válida de los servicios (o también llamados controladores) es trabajar con funciones los callbacks. El código queda limpio igualmente y puede ser una mejor introducción para los chicos/as que están iniciando con esta forma de programar de forma modular.
Tengan en cuenta que debido a los cambios en la libreria Faker ya no se usa el
faker.datatype.uuid
sino
faker.string.uuid
les dejo link de la info:
https://fakerjs.dev/api/datatype.html#uuid
El código quedó medio raro porque use mucho la palabra “service” 😂, pero ya lo había definido así
class ServicesService {
constructor(){
this.services = [
{
id: '1',
service: "Diseño de Cejas",
category: "Cejas",
price: 7000,
durationMin: 5
},
{
id: '2',
service: "Pigmentación de Cejas",
category: "Cejas",
price: 5000,
durationMin: 15
},
{
id: '3',
service: "Corte de cabello",
category: "Cabello",
price: 20000,
durationMin: 30
},
{
id: '4',
service: "Secado de cabello",
category: "Cabello",
price: 12000,
durationMin: 30
}
];
}
create() {};
find() {
return this.services;
};
findOne(id) {
return this.services.find(item => item.id === id );
};
update() {};
delete() {};
};
module.exports = ServicesService;
Me conflictua bastante ver POO en JS , cuando JS no es un lenguaje orientado a objetos, si no funcional. me gustaria debatir esto y de verdad JS no POO o OOP
const { faker } = require("@faker-js/faker");
class ProductsService {
constructor(){
this.products = [];
this.generate();
}
generate(){
const limit = 100;
for (let i = 0; i < limit; i++) {
this.products.push({
id: faker.datatype.uuid(),
name: faker.name.fullName(),
price: parseInt(faker.commerce.price(), 10),
image: faker.image.imageUrl()
});
}
}
create(){
}
find(){
return this.products;
}
findOne(id){
return this.products.find(item => item.id === id);
}
update(){
}
delete(){
}
}
module.exports = ProductsService;
APORTE
si no quieres ver en tus require lo siguiente
const example = require('./../services/product.service')
puedes crear un archivo llamado jsconfig.json en la raiz de tu proyecto con el siguiente codigo.
{
"compilerOptions": {
"baseUrl": "root_project",
}
}
donde root_project sera el path de donde quieres llamar a todos tus archivos, ejemplo quiero que todos mis archivos sean llamados desde mi raiz, podemos colocar la siguiente configuracion.
{
"compilerOptions": {
"baseUrl": ".",
}
}
Al estar el archivo jsconfig.json en la raiz el punto hace referencia a si mismo, pudiendo entonces requerir nuestro archivo service de ejemplo de la siguiente forma.
const example = require('services/product.service')
Evitar los import relativos nos puede ayudar a comprender mejor donde estan nuestros archivos requeridos,
Recuerden que si quieren usar Faker sin ningún inconveniente, deben descargarlo como
npm i faker@5.5.3
Especificando la versión, porque la 6.6.6 no funciona
Fecha: 13-2-2022
8:59
Un string super largo que va a actuar de forma randomnica
😭🤣
Aun no termino el curso y ya estoy maravillado de este curso, no entiendo como no lanzaron antes este curso, buenisimo el profesor y el contenido.
muy genial todo.
Acá les dejo el generador de usuarios con algunos campos importantes, espero les sirva 😄
generate(){
const limit = 20;
for(let i=0;i<limit;i++){
this.users.push({
id: faker.datatype.uuid(),
name: faker.name.firstName(),
lastName: faker.name.lastName(),
avatar: faker.image.imageUrl(),
city: faker.address.cityName(),
email: faker.internet.email(),
})
}
}
Excelente profesor. Muy bien explicado todo el curso hasta ahora un tema que en un principio me había resultado super complicado.
Les comparto mi código con mejoras para realizar un limit
class ProductsService{
constructor(){
this.products = [];
this.generate();
}
generate(limit){
console.log(limit)
for(let index = 0; index<(limit||1000); index++){
this.products.push({
id:faker.datatype.uuid(),
name:faker.commerce.productName(),
price: parseInt(faker.commerce.price(),10),
image:faker.image.imageUrl()
});
}
}
find(limit){
return this.products.slice(0,limit)
}
}
module.exports = ProductsService;```
``` const express = require('express');
const ProductsService = require('./../services/product.service')
const router = express.Router();
const service = new ProductsService();
router.get('/',(req,res)=>{
let {limit,page} =req.query;
limit = limit || 10;
const products = service.find(limit);
res.json({
size:products.length,
products})
})
module.exports = router;
faker.datatype.uuid()
está obsoleto, hay que usar en su lugar
faker.string.uuid()
const faker = require('faker');
class ProductsService {
constructor(){
this.products = [];
this.generate();
}
generate() {
const limit = 100;
for (let index = 0; index < limit; index++) {
this.products.push({
id: faker.datatype.uuid(),
name: faker.commerce.productName(),
price: parseInt(faker.commerce.price(), 10),
image: faker.image.imageUrl(),
});
}
}
create() {
}
find() {
return this.products;
}
findOne(id) {
return this.products.find(item => item.id === id);
}
update() {
}
delete() {
}
}
module.exports = ProductsService;
cuando avancemos para hacerlo metodos para otras rutas, como la de users, categories, etc. tendriamos que repetir todas las funciones de crear, borrar y demas. por lo que es preferible que esos metodos sean una clase por si sola y cada una de nuestros services hereden esos metodos, asi ahorramos limeas y es mas mantenible, al final el crud va a funcionar casi igual para todos los services de ruta,
to lo hice de esta manera:
// este es mi service de propducts
//yo use la importacion de modulos, ustedes pueden importar como quieran
import { faker } from "@faker-js/faker";
import { CrudService } from "./crud.service.js";
class ProductsService extends CrudService {
constructor() {
super();
this.generate();
}
generate() {
for (let index = 0; index < 5; index++) {
this.data.push({
id: faker.datatype.uuid(),
name: faker.commerce.productName(),
price: parseInt(faker.commerce.price()),
image: "url",
});
}
}
//este template sirve para unos metodos que usare en el crud
itemTemplate() {
return {
id: "",
name: "",
price: "",
image: "",
};
}
}
export { ProductsService };
import { faker } from "@faker-js/faker";
class CrudService {
constructor() {
//aqui tenemos el array de los datos, asi se hereda uno para cada service
this.data = [];
}
//recivo el item
create(item) {
try {
//le creo un id porque cuando creamos no debemos un item
//desde el front no deveriamos mandar le id,
//se deberia generar automatico
const id = { id: faker.datatype.uuid() };
//el item que mandan le anidamos el id
const newItem = {
...id,
...item,
};
//ahora traemos las prop que deberia tener un item
const props = Object.keys(this.itemTemplate());
//verificamos que el item tiene todas las props necesarias para ser guardado
for (let prop of props) {
if (!newItem.hasOwnProperty(prop)) {
//si no las tiene retirna el error
throw new Error(`La propiedad '${prop}' no está presente.`);
}
}
//si esta todo bien agregamos el item a la data
this.data.push(newItem);
//retorno si salio bien el porceso y el item qeu se aguardo
return [true, newItem];
} catch (error) {
return [error];
}
}
find() {
return this.data;
}
findOne(id) {
return this.data.find((item) => item.id === id);
}
//recibo el id
delete(id) {
try {
//verifico que el id exista
const existId = this.data.findIndex((item) => item.id === id);
if (existId === -1) {
//si no existe retornamos error
throw new Error(`No se encontró el ID '${id}'.`);
} else {
//si existe entonces guardamos todos los items que no tengan ese id
const updateProduct = this.data.filter((item) => item.id !== id);
//guardamos los daros actualizados
this.data = [...updateProduct];
return true;
}
} catch (error) {
return error;
}
}
}
export { CrudService };
si tienen alguna correcion me pueden decir :3
La parte del NOT FOUND la hice de la siguiente manera, no sé si lo explican más adelante pero, acá lo dejo.
router.get('/:id', (req, res) =>{
const { id } = req.params;
const product = service.findOne(id);
if(product){
res.status(StatusCodes.OK).json(product);
}else{
res.status(StatusCodes.NOT_FOUND).json({message: 'Not Found'})
}
});
muy Interesante esta clase
Me gusta mucho cómo queda el código después de aplicar clean arquitecture 👏🏼
Mi solución antes de ver el siguiente vídeo:
products router
const express = require('express');
const router = express.Router();
const ProductsService = require('../services/product.service')
const service = new ProductsService();
router.get('/', (req, res) => {
const products = service.findAll()
res.json(products)
})
//! Rutas específicas ANTES que rutas dinámicas
router.get('/:id', (req, res) => {
const { id } = req.params; //* tiene que ser igual que en los query params
const product = service.findOne(id)
res.json(product)
})
router.post('/', (req, res) => {
const body = req.body
const { name, price, image } = body
service.create(name, price, image)
res.status(201).json({
message: 'product created',
data: body
})
})
router.patch('/:id', (req, res) => {
const { id } = req.params
const body = req.body
const { name, price, image } = body
service.update(id, name, price, image)
res.json({
message: 'updated partial',
data: body,
id
})
})
router.delete('/:id', (req, res) => {
const { id } = req.params
service.delete(id)
res.json({
message: 'deleted',
id
})
})
module.exports = router;
products service:
const { faker } = require('@faker-js/faker')
class ProductsService {
constructor() {
this.products = []
this.generate() //* Creará los productos iniciales
}
generate() {
const limit = 100
for(let index = 0; index < limit; index++) {
this.products.push({
id: faker.datatype.uuid(),
name: faker.commerce.productName(),
price: parseInt(faker.commerce.price(), 10),
image: faker.image.imageUrl(),
})
}
}
create(name, price, image) {
this.products.push({
id: faker.datatype.uuid(),
name,
price,
image
})
}
findAll() {
return this.products
}
findOne(id) {
return this.products.find(item => item.id === id)
}
update(id, name, price, image) {
const product = this.findOne(id)
name !== undefined ? product.name = name : null
price !== undefined ? product.price = price : null
image !== undefined ? product.image = image : null
return product
}
delete(id) {
const product = this.findOne(id)
this.products.splice(product, 1)
}
}
module.exports = ProductsService
Esto va excelente 😄
<
router.get('/:id', (req,res) => {
const {id} = req.params;
const product = service.findOne(id);
if(product === undefined){
res.status(404).json({
message:'Product not found, please check your id'
});
}else{
res.status(200).json(product);
}
});
>
yo le añadí validación al método findOne en caso de no encontrar el producto con el id recibido en los parámetros
findOne(id){
const find = this.products.find(el => el.id == id)
if (find){
return find
}
return {
message: 'not found'
}
}
class ProductsServices {
constructor(){
this.products = []
this.generate()
}
generate(){
for (let i = 0; i < 10; i++){
this.products.push({
id: faker.datatype.uuid(),
name: faker.commerce.productName(),
price: faker.commerce.price(),
image: faker.image.imageUrl()
})
}
}
find(){
return this.products
}
findOne(id){
const find = this.products.find(el => el.id == id)
if (find){
return find
}
return {
message: 'not found'
}
}
}
Yo lo hice con query params
router.get('/', (req, res)=>{
const id = req.query.id || null;
if(id === null){
const products = service.find();
res.json(products)
}
else{
const products = service.findOne(id);
res.json(products);
}
})
Existe una extensión en visual llamada Thunder, para hacer peticiones, no hace falta abrir otra cosa.
“Controllers” == “Routes”
Tengo una gran duda, si es que pongo este código:
generate() {
let limit = 20
while(limit > 0) {
//Usamos los métodos de faker el cual nos genera datos aleatorios de un ecommerce
this.products.push({
id: parseInt(Math.random() * 23 ).toString() || faker.datatype.uuid(),
name: faker.commerce.productName(),
price: faker.commerce.price(),
url: faker.image.imageUrl()
})
limit--
}
}
Esta todo bien, pero si le cambio el nombre a las keys por ejemplo por estas
generate() {
let limit = 20
while(limit > 0) {
//Usamos los métodos de faker el cual nos genera datos aleatorios de un ecommerce
this.products.push({
id: parseInt(Math.random() * 23 ).toString() || faker.datatype.uuid(),
productName: faker.commerce.productName(),
price: faker.commerce.price(),
productURL: faker.image.imageUrl()
})
limit--
}
No me crea los registros, alguna idea?
Para el caso de la consulta de producto:
Recibo el id, instancio la clase y hago una validacion, en caso de encontrar el producto por el id que se envio retorno un status 200 mas el producto.
Caso constrario retorno un status 401 y un mensaje.
router.get('/:id', (req, res) => {
const { id } = req.params
const product = service.findOne(id)
if(product) res.status(200).json(product)
else res.status(401).json({message:"not found"})
})
Generador para Categorias
generate() {
const limit = 10;
for (let i = 0; i < limit; i++) {
this.categories.push({
id: faker.datatype.uuid(),
nombre: faker.commerce.productMaterial()
});
}
}
Una validación simple para cuando se requiera un producto inexistente:
router.get('/:id', (req, res) => {
const { id } = req.params;
const product = service.findOne(id);
if (product) {
res.status(200).json(product);
} else {
res.status(404).json({ message: 'Product not found' });
}
});
Creo que tenía una muy mala práctica y es que combinaba la capa de servicios con los controladores.
Controladores -> Services -> Libs
Esta organización de carpetas y archivos hace hermosa la aplicacion
Muy buena explicación del funcionamiento de los servicios.
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?