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?
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
¡Es tu turno: crea un tutorial!
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
Aportes 46
Preguntas 15
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;
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. 😁
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.
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.
Hasta este punto me siento super bien, con la forma de explicar de este profe, grande Nico!
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 :( ",
})
}
})
Les comparto mi código con mejoras para realizar un limit
👀 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 ✍️😉
Felicitationes Nicolas, excelente profesor.
Profe que se me hace que ese sweater se le va a perder 👀…
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
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 [email protected]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
😭🤣
Excelente, ahora entiendo cuándo se refieren a servicios.
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;
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.
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;
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'
“Controllers” == “Routes”
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
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?
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;
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?
o inicia sesión.