Aplicaciones Web

Edición - 2021

Aplicaciones Web

Unidad I:  Introducción

Unidad II: Backend

Unidad III: Frontend

Unidad IV: Despliegue en producción

Recursos

1.  Instalador de node.js

2.  Git

3.  Postman

Recursos

Extensiones de VSC

  • ES7 React/Redux/GraphQL/React-Native snippets
  • Bracket Pair Colorizer
  • Auto Rename Tag
  • JavaScript (ES6) code snippets
  • DotENV
  • Prettier - Code formatter
 

Orientaciones generales

  • Iniciaremos con Vanilla Node
  • Vamos a programar desde cero hasta avanzado
  • Repetir las prácticas es crucial para entender conceptos y no abrumarse
  • Es importante tomar notas de las lecturas obligatorias
  • Aún más importante es aplicar los conocimientos adquiridos en su proyecto de fin de curso y
  • Compartir lo aprendido con sus compañeros a través de la retroalimentación.
  • Todo es trabajo colaborativo

 

Unidad I: Introducción

Objetivo

Conocer los conceptos básicos relacionados con el desarrollo de Aplicaciones Web

¿Qué es una Aplicación Web?

Las aplicaciones web son un tipo de software que se codifica en un lenguaje soportado por los navegadores web y cuya ejecución es llevada a cabo por el navegador en Internet o de una intranet (de ahí que reciban el nombre de App web).

¿Cómo funcionan?

Las aplicaciones web se ejecutan por medio de un navegador web y no necesitan ser instaladas en tu pc o smartphone, ya que los datos o archivos utilizados están almacenados en una red o en la nube (Hosting).

 

Las aplicaciones web se relacionan estrechamente  con el almacenamiento de datos en la nube, ya que toda la información requerida esta en servidores web, que además de alojar la información, la envían a nuestros dispositivos cuando es requerida.

¿Cómo funcionan?

Tipo de App web que se puede crear

  • Aplicaciones para gestión interna: facturación, stock, clientes, usuarios, socios, contabilidad, gestión de personal, etc.
  • Herramientas de trabajo: intranets, gestión documentos, trabajo en red, servicios compartidos por múltiples usuarios.
  • Herramientas de comunicación digital como: mails, boletines digitales, comunicaciones personalizadas, etc.
  • Herramientas web como: tiendas virtuales, repositorios y buscadores.
  • Otros tipos de servicios: gestión de inmuebles, comunidades de propietarios, turismo, mapas, formación, colegios y mucho más .

Ventajas de las App Web

  • No necesita instalación ya que accedes a través de un navegador.
  • Una aplicación web es multiplataforma y multidispositivo.
  • Nuestro ordenador o dispositivo no se afecta en su memoria por el peso de la aplicación, ya que esta se soporta en el servidor donde este alojada.
  • La aplicación puede estar en la nube, accesible para cualquier ordenador o dispositivo que tenga acceso a Internet. También podría ser una aplicación local en una intranet.
  • Es muy adaptable y muy fácil de actualizar.

 

Lectura obligatoria: https://wiboomedia.com/que-son-las-aplicaciones-web-ventajas-y-tipos-de-desarrollo-web/

Arquitectura Cliente Servidor

La arquitectura cliente-servidor es un modelo de diseño de software en el que las tareas se reparten entre los proveedores de recursos o servicios, llamados servidores, y los demandantes, llamados clientes. Un cliente realiza peticiones a otro programa, el servidor, quien le da respuesta.

¿Qué es HTTP?

  • Protocolo de Transferencia de Hipertexto
  • Permite la comunicación entre cliente/servidor
  • HTTP Request/Responses
  • Incluye Header y Body

Ejemplo

const http = require('http')

const server = http.createServer(function (req, res) {
  console.log(req)
})

const PORT = 5000

server.listen(PORT, function () {
  console.log('El servidor está corriendo en el puerto ' + PORT)
})

¿Qué es node?

node.js podríamos decir que es un entorno de código abierto (Open Source), multiplataforma y que ejecuta el código Javascript fuera de un navegador.

 

Y es precisamente la necesidad de ejecutar este lenguaje del lado del servidor el que hace surgir Node.js.

 

Node soporta protocolos TCP, DNS y HTTP.

 

Lectura obligatoria: https://dev.to/denispixi/que-es-node-js-y-como-funciona-46ck

Frontend - Backend

Cliente

Servidor

Frontend

Front End es la parte de una aplicación que interactúa con los usuarios, es conocida como el lado del cliente. Básicamente es todo lo que vemos en la pantalla cuando accedemos a un sitio web o aplicación: tipos de letra, colores, adaptación para distintas pantallas(RWD), los efectos del ratón, teclado, movimientos, desplazamientos, efectos visuales… y otros elementos que permiten navegar dentro de una página web.

 

Este conjunto crea la experiencia del usuario.

Backend

El back end del sitio web consiste en un servidor, una aplicación y una base de datos. Se toman los datos, se procesa la información y se envía al usuario.  

 

 

 

 Un desarrollador Full Stack es el encargado de manejar cada uno de los aspectos relacionados con la creación y el mantenimiento de una aplicación web. Para ello es fundamental que tenga conocimientos en desarrollo Front-End y Back-End además de manejar diferentes sistemas operativos y lenguajes de programación.

¿Qué es una API?

Una API (Application Programming Interfaces) es un conjunto de procedimientos y funciones creados para permitir el acceso al backend de aplicaciones de terceros con el fin de reutilizar servicios ya creados.

 

Ejemplos: Facebook, Twitter, Google Maps, etc.

¿Qué es una API?

API (interfaz del programa de aplicación) es un conjunto de reglas que permite que diferentes programas se comuniquen entre sí.

 

Describe la manera apropiada para que un desarrollador de software componga un programa en un servidor que se comunica con varias aplicaciones cliente.

¿Qué es una API?

¿Qué es Postman?

Postman nace como una herramienta que principalmente nos permite crear peticiones sobre APIs de una forma muy sencilla y poder, de esta manera, probar las APIs.

Backend

El back end del sitio web consiste en un servidor, una aplicación y una base de datos. Se toman los datos, se procesa la información y se envía al usuario.  

 

 

 

Teamplate String

(Plantillas literales)

Las plantillas literales son cadenas literales que habilitan el uso de expresiones incrustadas.

// ...
const PORT = 5000

server.listen(PORT, function () {
  console.log(`El servidor está corriendo en el puerto ${PORT}`)
})

Desestructuración

La desestructuración es una expresión de JavaScript que permite desempacar valores de arreglos o propiedades de objetos en distintas variables

const server = http.createServer(function (req, res) {
  const { method, headers, url } = req
  console.log(method, headers, url)
  res.end()
})

Configurar el backend

npm init

Lo primero es inicializar nuestro proyecto. Esto crea un archivo package.json

¿Qué es el package.json?

De cierta forma, podemos considerar este package.json como un manifiesto de nuestro proyecto.

¿Qué es el package.json?

Node usa la herramienta npm. Esta herramienta, que normalmente se instala junto con Node, tiene dos roles fundamentales:

  1. Manejar la publicación de un proyecto al registro público de npm (para que otros puedan descargarlo y utilizarlo como dependencia en sus propios proyectos).

  2. Administrar las dependencias de tu proyecto.

 

Para esto, guarda un registro en un archivo llamado, justamente, package.json.

¿Qué es el package.json?

Dentro de este archivo se definen y manejan características como:

  • Nombre de tu proyecto.

  • Versión.

  • Dependencias.

  • Repositorio.

  • Autores.

  • Licencia.
    Y más.

Para crearlo normalmente se usa un asistente.

¿Qué es npm?

De sus siglas NPM (Node Package Manager) es un gestor de paquetes desarrollado en su totalidad bajo el lenguaje JavaScript por Isaac Schlueter.

 

 

A través de NPM podemos obtener cualquier librería con tan solo una sencilla línea de código, lo cual nos permitirá agregar dependencias de forma simple, distribuir paquetes y administrar eficazmente tanto los módulos como el proyecto a desarrollar en general.

Configurar Nodemon

npm install --save nodemon

En la terminal

  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  },

En package.json

npm run dev

En la terminal

Nodemon es una utilidad que monitorea los cambios en el código fuente que se esta desarrollando y automáticamente reinicia el servidor.

Responder con datos

const http = require('http')

const todos = [
  {
    id: 1,
    text: 'Ir al supermercado',
  },
  {
    id: 2,
    text: 'Terminar la tarea de sobre API de Aplicaciones Web',
  },
  {
    id: 3,
    text: 'Bañar al perro',
  },
]

const server = http.createServer(function (req, res) {
  // res.setHeader('Content-type', 'text/plain')
  // res.setHeader('Content-type', 'text/html')
  res.setHeader('Content-type', 'application/json')
  res.setHeader('X-Powered-By', 'Node.js')
  // res.write('<h1>Hola</h1>')
  // res.write('<h2>Hola de nuevo</h2>')

  res.end(JSON.stringify({ success: true, data: todos }))
})

const PORT = 5000

server.listen(PORT, function () {
  console.log(`El servidor está corriendo en el puerto ${PORT}`)
})

¿Qué es JSON?

JSON, cuyo nombre corresponde a las siglas JavaScript Object Notation o Notación de Objetos de JavaScript, es un formato ligero de intercambio de datos, que resulta sencillo de leer y escribir para los programadores y simple de interpretar y generar para las máquinas.

¿Qué es JSON?

JSON es un formato de texto completamente independiente de lenguaje

Códigos de estados HTTP

Los más importantes

Los códigos de estado de respuesta HTTP indican si se ha completado satisfactoriamente una solicitud HTTP específica. Las respuestas se agrupan en cinco clases:

  1. Respuestas informativas (100–199),
  2. Respuestas satisfactorias (200–299),
  3. Redirecciones (300–399),
  4. Errores de los clientes (400–499),
  5. y errores de los servidores (500–599).

Métodos de petición HTTP

GET               

POST             

PUT/PATCH

DELETE   

Recibir un recurso

Enviar un recurso

Actualizar recurso

Borrar/destruir recurso

Códigos de estados HTTP

Los más importantes

1.xx Respuesta Informativa

 

2.xx Respuesta satisfactoria

200 (OK) La solicitud ha tenido éxito.

201 Created

204 Not content

 

3.xx Redirección

304 Not modified. 

4.xx Errores de cliente

400 Bad request401

401 Unauthorized

404 Not found

 

5.xx Errores de servidor

500 Internal Server Error

 

Ejemplo

const http = require('http')

const todos = [
  {
    id: 1,
    text: 'Ir al supermercado',
  },
  {
    id: 2,
    text: 'Terminar la tarea de sobre API de Aplicaciones Web',
  },
  {
    id: 3,
    text: 'Bañar al perro',
  },
]

const server = http.createServer(function (req, res) {
  res.writeHead(404, {
    'Content-type': 'application/json',
    'X-Powered-By': 'Node.js',
  })

  res.end(
    JSON.stringify({
      success: false,
      error: 'No se encontró el recurso solicitado',
      data: null,
    })
  )
})

const PORT = 5000

server.listen(PORT, function () {
  console.log(`El servidor está corriendo en el puerto ${PORT}`)
})

Enviar datos al servidor

// ...
  
// console.log(req.headers.authorization)

  let body = []

  req
    .on('data', (chunk) => {
      body.push(chunk)
    })
    .on('end', () => {
      body =  
      console.log(body)
    })

// ...

Estándar RESTFULL API

GET  /todos

GET /todos/1           

POST /todos             

PUT/PATCH /todos/1

DELETE  /todos/1  

Obtener todos

Obtener todo con id 1

Agregar todo

Actualizar todo con id 1

Borrar todo con id 1

Ejemplo

const { equal } = require('assert')
const http = require('http')

const todos = [
  {
    id: 1,
    text: 'Ir al supermercado',
  },
  {
    id: 2,
    text: 'Terminar la tarea de sobre API de Aplicaciones Web',
  },
  {
    id: 3,
    text: 'Bañar al perro',
  },
]

const server = http.createServer(function (req, res) {
  const { method, url } = req

  let body = []

  req
    .on('data', (chunk) => {
      body.push(chunk)
    })
    .on('end', () => {
      body = Buffer.concat(body).toString()

      let status = 404

      const response = {
        success: false,
        data: null,
        error: null,
      }

      if (method === 'GET' && url === '/todos') {
        status = 200
        response.success = true
        response.data = todos
      } else if (method === 'POST' && url === '/todos') {
        const { id, text } = JSON.parse(body)

        if (!id || !text) {
          status = 400
          response.error = 'Por favor agregar id y texto del todo'
        } else {
          todos.push({ id, text })
          status = 201
          response.success = true
          response.data = todos
        }
      }

      res.writeHead(status, {
        'Content-type': 'application/json',
        'X-Powered-By': 'Node.js',
      })

      res.end(JSON.stringify(response))
    })
})

const PORT = 5000

server.listen(PORT, function () {
  console.log(`El servidor está corriendo en el puerto ${PORT}`)
})

Arrow function

(Función de flecha)

Una expresión de función flecha es una alternativa compacta a una expresión de función tradicional.

Callback

Una función de callback es una función que se pasa a otra función como un argumento, que luego se invoca dentro de la función externa para completar algún tipo de rutina o acción.

Unidad II: Backend

Objetivo

Desarrollar el Backend del proyecto de fin de curso

Plan del Proyecto MBShop

Express y dotenv básico

// Crear el folder backend

npm init
npm install express dotenv
npm install -D nodemon

¿Qué es express?

Expressjs es un framework rápido, minimalista y flexible de Node.js. Permite crear APIs y aplicaciones web fácilmente, provee un conjunto de características como manejo de rutas (direccionamiento), archivos estáticos, integración con bases de datos, manejo de errores, middlewares entre otras.

¿Qué es una variable de entorno?

Las variables de entorno nos permiten administrar la configuración de nuestras aplicaciones por separado de nuestro código base.

 

Las configuraciones separadas facilitan la implementación de nuestra aplicación en diferentes entornos (development, test, production, etc).

Configuración básica de express

const express = require('express')
const dotenv = require('dotenv')

// Load env vars
dotenv.config({ path: './config/config.env' })

const app = express()

const PORT = process.env.PORT || 5000

app.listen(PORT, () => {
  console.log(`Server is running in ${process.env.NODE_ENV} on port ${PORT}`)
})

server.js

¿Qué es git?

Git es un software de control de versiones diseñado por Linus Torvalds, pensando en la eficiencia, la confiabilidad y compatibilidad del mantenimiento de versiones de aplicaciones cuando éstas tienen un gran número de archivos de código fuente.

¿Qué es git?

Rutas

Estructura

/api/v1/products

/api/v1/users

/api/v1/orders

...

Modelo MVC

Básicamente MVC es un patrón de diseño que propone separar nuestro código por responsabilidad.

Modelo MVC

Model: Es el encargado de manejar la información de nuestra aplicación (por ejemplo, se encarga de guardar información en un archivo).


View: Es lo que el usuario ve y con lo que este interactúa.


Controller: Es el encargado de unir el Model y la View, y también posee la lógica para transformar los datos para que los entienda tanto el Model como la View.

Métodos controladores

Controllers

Los encargados de la lógica...

Relación entre node y express

Middleware

Middleware en Express JS. Un middleware es una función que se puede ejecutar antes o después del manejo de una ruta.

MongoDB

Introducción a MongoDB (I)

Introducción a MongoDB (II)

(records)

Introducción a MongoDB (III)

Instalación de MongoDB

  1. Crear una cuenta en https://www.mongodb.com/
  2. Ir al menú cloud/atlas, buscar la opción log in here

Datos de ejemplo 

products.js

[
  {
    "name": "Auriculares inalámbricos Bluetooth Airpods",
    "image": "/images/airpods.jpg",
    "description": "La tecnología Bluetooth le permite conectarlo con dispositivos compatibles de forma inalámbrica El audio AAC de alta calidad ofrece una experiencia auditiva envolvente El micrófono incorporado le permite recibir llamadas mientras trabaja",
    "brand": "Apple",
    "category": "Electrónica",
    "price": 89.99,
    "countInStock": 3,
    "rating": 0,
    "numReviews": 0
  },
  {
    "name": "iPhone 11 Pro 256GB Memoria",
    "image": "/images/phone.jpg",
    "description": "Presentamos el iPhone 11 Pro. Un sistema transformador de triple cámara que agrega toneladas de capacidad sin complejidad. Un salto sin precedentes en la duración de la batería",
    "brand": "Apple",
    "category": "Electrónica",
    "price": 599.99,
    "countInStock": 10,
    "rating": 0,
    "numReviews": 0
  },
  {
    "name": "Cámara Cannon EOS 80D DSLR",
    "image": "/images/camera.jpg",
    "description": "Caracterizada por especificaciones de imagen versátiles, la Canon EOS 80D se aclara aún más mediante un par de sistemas de enfoque robustos y un diseño intuitivo",
    "brand": "Cannon",
    "category": "Electrónica",
    "price": 929.99,
    "countInStock": 0,
    "rating": 0,
    "numReviews": 0
  },
  {
    "name": "Sony Playstation 4 Pro versión blanca",
    "image": "/images/playstation.jpg",
    "description": "El centro de entretenimiento doméstico definitivo comienza con PlayStation. Ya sea que le gusten los juegos, las películas HD, la televisión, la música",
    "brand": "Sony",
    "category": "Electrónica",
    "price": 399.99,
    "countInStock": 10,
    "rating": 0,
    "numReviews": 0
  },
  {
    "name": "Mouse para juegos Logitech G-Series",
    "image": "/images/mouse.jpg",
    "description": "Controle mejor sus juegos con este mouse para juegos Logitech LIGHTSYNC. Los seis botones programables permiten la personalización para una experiencia de juego fluida",
    "brand": "Logitech",
    "category": "Electrónica",
    "price": 49.99,
    "countInStock": 7,
    "rating": 0,
    "numReviews": 0
  },
  {
    "name": "Amazon Echo Dot 3ra Generación",
    "image": "/images/alexa.jpg",
    "description": "Conoce a Echo Dot: nuestro altavoz inteligente más popular con un diseño de tela. Es nuestro altavoz inteligente más compacto que se adapta perfectamente a espacios reducidos",
    "brand": "Amazon",
    "category": "Electrónica",
    "price": 29.99,
    "countInStock": 0,
    "rating": 0,
    "numReviews": 0
  }
]

Operador spread

Video de análisis obligatorio: https://youtu.be/gFvWNjVy-wc

Promesas

Video de análisis obligatorio: https://youtu.be/5XyzLfPBpZs

Archivos en node

UTF-8

Video de análisis obligatorio: https://youtu.be/JG4FtfAiPE4

Expresiones regulares

Video de análisis obligatorio: https://youtu.be/wfogZfIS03U

Las expresiones regulares son patrones que se utilizan para hacer coincidir combinaciones de caracteres en cadenas.

Expresiones regulares

Video de análisis obligatorio: https://youtu.be/wfogZfIS03U

Las expresiones regulares son patrones que se utilizan para hacer coincidir combinaciones de caracteres en cadenas.

Expresión para validar email

/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/

Ecriptación

JSON Web Token

Se define un mecanismo para poder propagar entre dos partes, y de forma segura, la identidad de un determinado usuario, además con una serie de claims o privilegios.

Data

[
  {
    "_id": "6041ab94157a1f54e0e75666",
    "name": "Auriculares inalámbricos Bluetooth Airpods",
    "image": "/images/airpods.jpg",
    "description": "La tecnología Bluetooth le permite conectarlo con dispositivos compatibles de forma inalámbrica El audio AAC de alta calidad ofrece una experiencia auditiva envolvente El micrófono incorporado le permite recibir llamadas mientras trabaja",
    "brand": "Apple",
    "category": "Electrónica",
    "price": 89.99,
    "countInStock": 3,
    "rating": 0,
    "numReviews": 0,
    "user": "6041a8da4e0425528d2454ed"
  },
  {
    "_id": "6041ab75157a1f54e0e75664",
    "name": "iPhone 11 Pro 256GB Memoria",
    "image": "/images/phone.jpg",
    "description": "Presentamos el iPhone 11 Pro. Un sistema transformador de triple cámara que agrega toneladas de capacidad sin complejidad. Un salto sin precedentes en la duración de la batería",
    "brand": "Apple",
    "category": "Electrónica",
    "price": 599.99,
    "countInStock": 10,
    "rating": 0,
    "numReviews": 0,
    "user": "6041a8da4e0425528d2454ed"
  },
  {
    "_id": "6041a8cf33f6a452cad6d885",
    "name": "Cámara Cannon EOS 80D DSLR",
    "image": "/images/camera.jpg",
    "description": "Caracterizada por especificaciones de imagen versátiles, la Canon EOS 80D se aclara aún más mediante un par de sistemas de enfoque robustos y un diseño intuitivo",
    "brand": "Cannon",
    "category": "Electrónica",
    "price": 929.99,
    "countInStock": 0,
    "rating": 0,
    "numReviews": 0,
    "user": "6041a8da4e0425528d2454ed"
  },
  {
    "_id": "6041a8cf33f6a452cad6d886",
    "name": "Sony Playstation 4 Pro versión blanca",
    "image": "/images/playstation.jpg",
    "description": "El centro de entretenimiento doméstico definitivo comienza con PlayStation. Ya sea que le gusten los juegos, las películas HD, la televisión, la música",
    "brand": "Sony",
    "category": "Electrónica",
    "price": 399.99,
    "countInStock": 10,
    "rating": 0,
    "numReviews": 0,
    "user": "6041a8da4e0425528d2454ed"
  },
  {
    "_id": "6041a8cf33f6a452cad6d883",
    "name": "Mouse para juegos Logitech G-Series",
    "image": "/images/mouse.jpg",
    "description": "Controle mejor sus juegos con este mouse para juegos Logitech LIGHTSYNC. Los seis botones programables permiten la personalización para una experiencia de juego fluida",
    "brand": "Logitech",
    "category": "Electrónica",
    "price": 49.99,
    "countInStock": 7,
    "rating": 0,
    "numReviews": 0,
    "user": "6041a8da4e0425528d24541a"
  },
  {
    "_id": "6041a8cf33f6a452cad6d884",
    "name": "Amazon Echo Dot 3ra Generación",
    "image": "/images/alexa.jpg",
    "description": "Conoce a Echo Dot: nuestro altavoz inteligente más popular con un diseño de tela. Es nuestro altavoz inteligente más compacto que se adapta perfectamente a espacios reducidos",
    "brand": "Amazon",
    "category": "Electrónica",
    "price": 29.99,
    "countInStock": 0,
    "rating": 0,
    "numReviews": 0,
    "user": "6041a8da4e0425528d24541a"
  }
]
[
  {
    "_id": "6041a8da4e0425528d2454ed",
    "name": "Joshua Bonilla",
    "email": "joshua@gmail.com",
    "password": "123456"
  },
  {
    "_id": "6041a8da4e0425528d24541a",
    "name": "Ashley López",
    "email": "ashley@gmail.com",
    "password": "123456"
  }
]

products.js

users.js

Data

[
  {
    "title": "Muy buen producto",
    "comment": "Tengo seis meses de usar el producto y me ha parecido excelente",
    "product": "6041ab75157a1f54e0e75664",
    "rating": 8,
    "user": "6041a8da4e0425528d2454ed"
  },
  {
    "title": "Excelente",
    "comment": "Calidad inigualable",
    "product": "6041ab75157a1f54e0e75664",
    "rating": 10,
    "user": "6041a8da4e0425528d24541a"
  }
]

reviews.js

JWT y Cookie

Unidad III: frontend

Objetivo

Desarrollar el Frontend del proyecto de fin de curso

¿Qué es React?

Es una librería Javascript focalizada en el desarrollo de interfaces de usuario (UI)

¿Qué es React?

  • Creada y mantenida por Facebook
  • Es uno de los Framework más populares de la industria

Otros

¿Por qué React?

  • Permite construir UI poderosas
  • Código organizado
  • Componentes reusables
  • Velocidad y rendimiento
  • Permite hacer las cosas de una forma más fácil

UI basada en componentes

  • Observa cada elemento como un componente individual
  •  Permite mantener una estructura organizada
  • Los componentes pueden tener "props" y "state"

Configurar entorno

  • Node
  • Visual Studio Code
  • Git (Versión y despliegue)
  • Postman
  • Chrome - React Developer Tools
  • Chrome - Redux DevTools
  • Extensiones - VSC
    • Bracket Pair Colorizer
    • Auto Rename Tag
    • Prettier
    • ES7 React/Redux/GraphQL/React-Native snippets
    • settings.js
      • "emmet.includeLanguages": {

        "javascript": "javascriptreact"

        },

¿Qué es JSX?

JSX es una extensión de sintaxis de JavaScript que nos permite mezclar JS y HTML (XML), de ahí su nombre JavaScript XML.

Herramientas

Rutas

react-router-dom

react-boostrap

Recordemos

Redux

Es un contenedor predecible de estados de nuestra aplicación

Conceptos - Redux 

Es un contenedor predecible de estados de nuestra aplicación

Store

Fuente única de la verdad

Conceptos - Redux 

Es una función pura que maneja los estados

Agregar producto

Reducer

State: [products]

  {
    "_id": "6041ab94157a1f54e0e75666",
    "name": "Auriculares inalámbricos Bluetooth Airpods",
    "image": "/images/airpods.jpg",
    "description": "La tecnología Bluetooth le permite conectarlo con dispositivos compatibles de forma inalámbrica El audio AAC de alta calidad ofrece una experiencia auditiva envolvente El micrófono incorporado le permite recibir llamadas mientras trabaja",
    "brand": "Apple",
    "category": "Electrónica",
    "price": 89.99,
    "countInStock": 3,
    "rating": 0,
    "numReviews": 0,
    "user": "6041a8da4e0425528d2454ed"
  },

state

Página o vista

Action

R

Reducer

R

R

R

 

Dipatcher

middlewares

API

Store

Crear la aplicación React

mkdir MBShop
cd MBShop
npx create-react-app frontend
cd frontend
npmstart

// Borrar archivo innecesarios del frontend
// Sacar el .gitignore a la raiz
// Agregar noe_modules/ y .env al .gitignore
// Borrar el .git
Ctrl + C
cd ..
git init
git add .
git commit -m 'React setup'
cd frontend
npm start

React Bootstrap

npm install react-bootstrap

React router

 npm install react-router-dom react-router-bootstrap

Consultar Backend desde Frontend

cd frontend
npm install axios

Nodemon y Concurrently

// Desde MBShop
npm i -D nodemon concurrently
// ... 
"scripts": {
  "start": "node backend/server",
    "server": "nodemon backend/server",
      "client": "npm start --prefix frontend",
        "dev": "concurrently \"npm run server\" \"npm run client\""
},
  
// ...

MBShop/package.json

Variables de entorno

// https://www.npmjs.com/package/dotenv

npm install dotenv
NODE_ENV = development
PORT = 5000

MBShop/.env

ES en módulos de node

"type": "module"

MBShop/package.json

MongoDB

BCrypt

npm i bcryptjs

Redux

Instalar Redux Dev Tools

Instalar Redux (Frontend)

cd frontend
npm i redux react-redux redux-thunk redux-devtools-extension

Autenticación

Autorización

npm i jsonwebtoken

Arquitectura de la aplicación

Generar nuestra app con express

const express = require("express");
const app = express();

app.get("/", (req, res) => {
    res.send({ hola: 'Mundo' });
});

app.listen(5000);

// Desde la consola ejecutamos el comando: node index.js
// En el navegador escribimos http://localhost:5000/

Rutas en express (Route handler)

Deployment en heroku

Deployment en heroku

En muchos entornos (por ejemplo, Heroku), y como convención, configuran la variable de entorno PORT para decirle a su servidor web en qué puerto escuchar.

 

Entonces process.env.PORT || 5000 significa: lo que sea que esté en la variable de entorno PORT, o 5000 si no hay nada allí.

const PORT = process.env.PORT || 5000;
app.listen(PORT);

modificamos index.js

Deployment en heroku

  "main": "index.js",
  "engines": {
    "node": "12.14.1",
    "npm": "6.13.4"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },

modificamos package.json

Agregar "engines" entre "main" y "script" para especificar el entorno que tenemos en local y evitar errores de compatibilidad en la versión en producción

Deployment en heroku

  "scripts": {
    "start": "node index.js"
  },

modificamos package.json

Dentro de "scripts" borramos "test" y agregamos "start" con el comando que hemos especificado para ejecutar index.js en local

Deployment en heroku

node_modules

Cuando instalamos dependencias como express, de forma común no se controla versión de los mismos, tampoco deben desplegarse en heroku, porque haremos que heroku haga las instalaciones por si mismo. Para eso es .gitignore

  1. Creamos el archivo .gitignore, desde visual studio code
  2. Dentro de .gitignore especificar el siguiente código

Desplegar App en heroku

Crear cuenta heroku

www.heroku.com

Hacer un commit - código base

git init
git add .
git commit -m "Commit inicial"

Instalar Heroku CLI

//Desde la línea de comandos
heroku -v
//Para hacer login solicitará el email y contraseña de nuestra cuenta heroku
heroku login
// Creamos la App. Heroku le asigna un nombre 
// y provee la ruta git para el diployment
heroku create

Desplegar la App con git

//Desde la línea de comandos
heroku -v
//Para hacer login solicitará el email y contraseña de nuestra cuenta heroku
heroku login
// Creamos la App. Heroku le asigna un nombre 
// y provee la ruta git para el diployment
heroku create
// Ejemplo de proyecto creado en Heroku
// https://fathomless-waters-86018.herokuapp.com/ | https://git.heroku.com/fathomless-waters-86018.git

// Agregar el repositorio remoto
git remote add heroku https://git.heroku.com/fathomless-waters-86018.git

// Pasar la información del repositorio local al remoto
git push heroku master

// Probamos la App
heroku open

// En caso de error
heroku logs

Introducción a la autenticación con Google OAuth

Recordemos el flujo

OAuth

Open Authorization es un estándar abierto que permite flujos simples de autorización para sitios web o aplicaciones informáticas. Se trata de un protocolo propuesto por Blaine Cook y Chris Messina, que permite autorización segura de una API de modo estándar y simple para aplicaciones de escritorio, móviles y web.

 

Tomado de: https://es.wikipedia.org/wiki/OAuth

Flujo de OAuth

Passport JS

Configurar Passport JS

npm install --save passport passport-google-oauth20 
const express = require('express');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

const app = express();

passport.use(new GoogleStrategy());

const PORT = process.env.PORT || 5000;
app.listen(PORT);

index.js

En la terminal

Habilitar la API Google OAuth

Hacer que Google conozca nuestra aplicación

  • Debe disponer de una cuenta en google (gmail)
  • Ir a: https://console.developers.google.com/
  • Crear el proyecto: mb-feedback-dev
  • Seleccionar el proyecto creado
  • Crear Pantalla de consentimiento de OAuth (Externa)

Habilitar la API Google OAuth

Hacer que Google conozca nuestra aplicación

  • Desde el menú credenciales seleccionamos Crear Credenciales / ID de Cliente de OAuth
  • Dejamos la configuración como se ve en la imagen
  • Luego de crear, copiamos nuestro ID de cliente y el código de cliente secreto, de  momento lo podemos pegar como comentario dentro del index.js para usarlos más adelante.

Asegurando las claves de la API

1. Crear el arhivo keys.js

module.exports = {
    googleClientID: '439988331208-j7m4lnq3m4sr14soce1ro8c7l41vvp6e.apps.googleusercontent.com',
    googleClientSecret: 'PUkKItVVoJ3nRQKqoTybgcWy'
}

2. Exportar claves para que puedan ser accedidas desde otros archivos

3. Ignorar las claves para que no estén disponibles en los diployment. Esto se hace desde el .gitignore

node_modules
keys.js

Opciones de Google Strategy

const express = require("express");
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const keys = require("./config/keys");

const app = express();

passport.use(
  new GoogleStrategy(
    {
      clientID: keys.googleClientID,
      clientSecret: keys.googleClientSecret,
      callbackURL: "/auth/google/callback"
    },
    accessToken => {
      console.log(accessToken);
    }
  )
);

const PORT = process.env.PORT || 5000;
app.listen(PORT);

Probar OAuth

app.get(
  "/auth/google",
  passport.authenticate("google", {
    scope: ["profile", "email"]
  })
);

Autorizar redirección de URI

Nota: este cambio puede tomar en algunos casos hasta 5 minutos para que google agregue la redirección.

OAuth Callback

app.get('/auth/google/callback', passport.authenticate('google'));

Ver código generado por el accessToken

access y refresh tokens

passport.use(
  new GoogleStrategy(
    {
      clientID: keys.googleClientID,
      clientSecret: keys.googleClientSecret,
      callbackURL: "/auth/google/callback"
    },
    (accessToken, refreshToken, profile, done) => {
      console.log('access token: ', accessToken);
      console.log('refresh token: ', refreshToken);
      console.log('profile: ', profile);
    }
  )
);

Estructura del servidor (I)

Estructura del servidor (II)

const express = require("express");
require('./services/passport');
// const authRoutes = require('./routes/authRoutes');

const app = express();

require('./routes/authRoutes')(app);

const PORT = process.env.PORT || 5000;
app.listen(PORT);
const passport = require('passport');

module.exports = app => {
    app.get(
        "/auth/google",
        passport.authenticate("google", {
        scope: ["profile", "email"]
        })
    );
    
    app.get('/auth/google/callback', passport.authenticate('google'));
}

index.js

authRoutes.js

Estructura del servidor (III)

const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const keys = require("../config/keys");

passport.use(
    new GoogleStrategy(
      {
        clientID: keys.googleClientID,
        clientSecret: keys.googleClientSecret,
        callbackURL: "/auth/google/callback"
      },
      (accessToken, refreshToken, profile, done) => {
        console.log('access token: ', accessToken);
        console.log('refresh token: ', refreshToken);
        console.log('profile: ', profile);
      }
    )
);

passport.js

Teoría sobre autenticación

Usando Cookies

Iniciar sesión con OAuth

Iniciar sesión con OAuth (II)

MongoDB

passport callback

// ... el código anterior queda igual

passport.use(
    new GoogleStrategy(
      {
        clientID: keys.googleClientID,
        clientSecret: keys.googleClientSecret,
        callbackURL: "/auth/google/callback"
      },
      (accessToken, refreshToken, profile, done) => {
        // Retorna una promesa
        User.findOne({ googleId: profile.id })
          .then( (existingUser) => {
            if(existingUser) {
              // Tenemos un registro con el Id de perfil
              // El primer parámtro indica que no hay error
              // El segundo parámetro indica a passport el 
              // usuario que hemos encontrado
              done(null, existingUser);
            } else {
              // No tenemos un registro del Id de Perfil, hacemos un nuevo registro
              new User({ googleId: profile.id })
                .save()
                .then(user => DelayNode(null, user));
            }
          }
        );
      }
    )
);

Serializar usuario (I)

Serializar usuario (II)

En passport.js

// ... el código anterio queda igual

const User = mongoose.model('users');

passport.serializeUser((user, done)=> {
  done(null, user.id);
});

// ... el resto de código queda igual

¿Por qué el ID interno y no el del perfil?

passport.serializeUser () configura el id como cookie en el navegador del usuario y passport.deserializeUser () obtiene la identificación de la cookie.

Deserializar usuario

En passport.js

// ... el código anterio queda igual
passport.serializeUser((user, done)=> {
  done(null, user.id);
});

// El primer pqarámetro del arrow function es el token (id)
// que deseamos buscar en la cookie
passport.deserializeUser( (id, done) => {
  User.findById(id)
    .then(user => {
      done(null, user);
    });
});

// ... el resto de código queda igual

Habilitar coockies

1. Desde la terminal

npm install --save cookie-session

2. En index.js

// ... el código anterior queda igual
const mongoose = require('mongoose');
const cookieSession = require('cookie-session');
const passport = require('passport');
// ... el código posterior queda igual

3. En keys.js, agregar coockieKey

module.exports = {
    //... lo anterior queda igual
    cookieKey: 'qiwpsbjtarsporle'
}
// ... el resto de código queda igual
const app = express();

app.use(
    cookieSession({
        maxAge: 30 * 24 * 60 * 60 * 1000,
        keys: [keys.cookieKey]
    })
);
app.use(passport.initialize());
app.use(passport.session());

4. En el index.js

Probar autenticación

app.get('/api/current_user', (req, res) => {
  res.send(req.user);
});

Agregar esta ruta en authRoute

Cerrar sesión

app.get('/api/logout', (req, res) => {
  req.logout();
  res.send(req.user);
});

Veamos esto en mayor profundidad

cookie-session

express-session

Keys de desarrollo (Dev) vs keys de producción (Prod)

Debe evitarse que si alguien accede a nuestra computadora pueda tomar las claves (keys) y afectar nuestro entorno de producción. Por eso en producción debemos tener otras keys por seguridad.

Keys de desarrollo (Dev) vs keys de producción (Prod)

Generar recursos de producción(I)

En la versión en producción deben usarse credenciales seguras. En nuestro caso es básica por fines de simplicidad.

Generar recursos de producción(II)

Generar recursos de producción(II)

1

2

3

Generar recursos de producción(III)

Generar recursos de producción(IV)

Desde la terminal ejecutamos el siguiente comando para obtener la ruta de la aplicación alojada en Heroku

heroku open

Generar recursos de producción(V)

Generar recursos de producción(VI)

Determinar el entorno

module.exports = {
    googleClientID: '439988331208-j7m4lnq3m4sr14soce1ro8c7l41vvp6e.apps.googleusercontent.com',
    googleClientSecret: 'PUkKItVVoJ3nRQKqoTybgcWy',
    mongoURI: 'mongodb://wilfredo:Password1.@ds061711.mlab.com:61711/mb-feedback-dev',
    cookieKey: 'qiwpsbjtarsporle'
}

En dev.js

module.exports = {
    googleClientID: process.env.GOOGLE_CLIENT_ID,
    googleClientSecret: process.env.GOOGLE_CLIENT_SECRET,
    mongoURI: process.env.MONGO_URI,
    cookieKey: process.env.COOKIE_KEY
}

En prod.js

node_modules
dev.js

En .gitignore

if(process.env.NODE_ENV === 'production') {
    module.exports = require('./prod');
} else {
    module.exports = require('./dev');
}

En keys.js

Variables en Heroku env (I)

1

2

3

Variables en Heroku env (II)

En la terminal

git status
git add .
git commit -m "Flujo Auth finalizado"
git push heroku master
heroku open

Reparar errores

new GoogleStrategy(
  {
    clientID: keys.googleClientID,
    clientSecret: keys.googleClientSecret,
    callbackURL: "/auth/google/callback",
    proxy: true
  },
  
  // el resto queda igual
git status
git add .
git commit -m "Ajuste al Google Strategy"
git push heroku master
heroku open

React

(El lado del cliente)

Generar la aplicación React

npm install -g create-react-app

Se recomienda que la consola tenga permisos de administrador

npx create-react-app my-app

// Client tiene su propio servidor
cd client
npm start

Front end separado

Se puede trabajar juntos, pero de forma separada create-react-app ahorra mucho tiempo con la configuración de dependencias.

Ejecutar cliente y sevidor

1. Se puede trabajar con dos consolas separadas

Consola 1 - en la carpeta de "client"

Consola 2 - desde la carpeta "server"

npm start
npm run dev
npm install --save concurrently

2. Usando concurrently

  "scripts": {
    "start": "node index.js",
    "server": "nodemon index.js",
    "client": "npm run start --prefix client",
    "dev": "concurrently \"npm run server\" \"npm run client\""
  },

En package.json

Desde server

npm run dev

Terminal

Enrutamiento - problemas

Si intentamos crear un enlace en el front-end que nos lleve a /auth/google tendremos problemas porque la ruta es manejada desde el backend, esta ruta puede cambiar según se trabaje en dev o prod

Solución: desde el client

cd client
npm install http-proxy-middleware --save

Ahora cree un archivo de configuración para su proxy. Debe nombrarlo setupProxy.js en su carpeta src en el lado del cliente y escriba el siguiente código:

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/auth/google',
    createProxyMiddleware({
      target: 'http://localhost:5000'
    })
  );
};

Bondades de create-react-app

cd client
npm run build

Nota: Hay que agregar el callback

Solución error en redirección

Desarrollar el lado del cliente

Sintaxis AsyncAwait (I)

function fetchAlbums() {
    fetch('http://rallycoding.herokuapp.com/api/music_albums')
    .then(res => res.json())
    .then(json => console.log(json));
}

fetchAlbums();

Sintaxis AsyncAwait (II)

async function fetchAlbums() {
    const res = await fetch('http://rallycoding.herokuapp.com/api/music_albums')
    const json = await res.json();

    console.log(json);
}

fetchAlbums();

Esta forma es más sencilla y es con la que trabajaremos de ahora en adelante:

const fetchAlbums = async () => {
    const res = await fetch('http://rallycoding.herokuapp.com/api/music_albums')
    const json = await res.json();

    console.log(json);
}

fetchAlbums();

Con arrow function

Refactoring con AsyncAwait

async (accessToken, refreshToken, profile, done) => {
  const existingUser = await User.findOne({ googleId: profile.id });

  if (existingUser) {
    // Tenemos un registro con el Id de perfil
    return done(null, existingUser);
  } 
  // No tenemos un registro del Id de Perfil, hacemos un nuevo registro
  const user = await new User({ googleId: profile.id }).save();
  done(null, user);
}

En passport.js

Nota: se requiere una versión de node mínima 8.1.1

Tecnologías Front-end (I)

Tecnologías Front-end (II)

Tecnologías Front-end (III)

Tecnologías Front-end (IV)

Cliente - configurar react (I)

Dejamos el directorio src de la siguiente forma (todo va desde cero):

Cliente - configurar react (II)

cd client
npm install --save redux react-redux react-router-dom

En index.js

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(); 

Instalar módulo raíz

2. En index.js

import React from "react";
import ReactDOM from "react-dom";

import App from "./components/App";

ReactDOM.render(<App />, document.querySelector("#root"));

Para exportar componentes los nombres de los archivos inician con mayúsculas con convensión

import React from 'react';

const App = () => {
    return (
        <div>
            Hola
        </div>
    )
}

export default App;

1. En App.js

3. En la Terminal

cd ..

Configuración de Redux (I)

Configuración de Redux (II)

Configuración de Redux (III)

Configuración de Redux (IV)

Configuración de Redux (V)

En index.js

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';

import App from "./components/App";

const store = createStore(() => [], {}, applyMiddleware());

ReactDOM.render(
    <Provider store={store}><App /></Provider>, 
    document.querySelector("#root")
);

El Auth Reducer

2. En authReducer.js

export default function(state = {}, action) {
    switch(action.type) {
        default:
            return state;
    }
}

1. Crear la siguiente estructura

3. En index.js del directorio reducers

import { combineReducers } from 'redux';
import authReducer from './authReducer';

export default combineReducers({
    auth: authReducer
});

4. Modificar en index.js de src

import App from "./components/App";
import reducers from './reducers';

const store = createStore(reducers, {}, applyMiddleware());

Consideraciones acerca de Auth

Configurar react router

import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';

const Header = () => <h2>Header</h2>;
const Dashboard = () => <h2>Dashboard</h2>;
const SurveyNew = () => <h2>SurveyNew</h2>;
const Landing = () => <h2>Landing</h2>;

const App = () => {
    return (
        <div>
            <BrowserRouter>
                <div>
                    <Route path="/" component={Landing} />
                </div>
            </BrowserRouter>
        </div>
    )
}

export default App;

En components/App.js

Rutas con exact

import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';

const Header = () => <h2>Header</h2>;
const Dashboard = () => <h2>Dashboard</h2>;
const SurveyNew = () => <h2>SurveyNew</h2>;
const Landing = () => <h2>Landing</h2>;

const App = () => {
    return (
        <div>
            <BrowserRouter>
                <div>
                    <Route exact path="/" component={Landing} />
                    <Route path="/surveys" component={Dashboard} />
                </div>
            </BrowserRouter>
        </div>
    )
}

export default App;

En components/App.js

Componentes siempre visibles

import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';

const Header = () => <h2>Header</h2>;
const Dashboard = () => <h2>Dashboard</h2>;
const SurveyNew = () => <h2>SurveyNew</h2>;
const Landing = () => <h2>Landing</h2>;

const App = () => {
    return (
        <div>
            <BrowserRouter>
                <div>
                    <Header />
                    <Route exact path="/" component={Landing} />
                    <Route exact path="/surveys" component={Dashboard} />
                    <Route path="/surveys/new" component={SurveyNew} />
                </div>
            </BrowserRouter>
        </div>
    )
}

export default App;

En components/App.js

Materialize (I)

import React, { Component } from 'react';

class Header extends Component {
    render(){
        return (
            <div>
                Header
            </div>
        );
    }
}

export default Header;

2. Header.js

1. En components/App.js

3. En App.js

// Reemplazar 
const Header = () => <h2>Header</h2>;
// por 
import Header from './Header';

Materialize (II)

Para la apariencia vamos a utilizar un framework de propósito general que puede utilizar con cualquier librería o framework JavaScript

https://materializecss.com/

  cd client
  npm install --save materialize-css

Desde la terminal

webpack

  • Para CSS debemos especificar la extensión, para js Webpack los asume por defecto y no es necesario reflejarlo.
  • Cuando se trata de un componente (ejemplo el de materialize-css) no es necesario especificar la ruta relativa "./ "
  • No hay asignación a variables por lo que puede omitirse.

 

En la primera línea de index.js dentro de src

import 'materialize-css/dist/css/materialize.min.css';

Diseñar el Header

import React, { Component } from 'react';

class Header extends Component {
    render(){
        return (
            <nav>
                <div className="nav-wrapper">
                <a href="#" className="left brand-logo">mb-feedback</a>
                <ul id="nav-mobile" className="right hide-on-med-and-down">
                    <li><a href="#">Login con Google</a></li>
                </ul>
                </div>
            </nav>
        );
    }
}

export default Header;
return (
  <div className="container">
  <BrowserRouter>

En App.js (Poner la clase container)

Reglas Proxy (I)

  cd client
  npm install --save axios redux-thunk

En la terminal

// ....
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';

import App from "./components/App";
import reducers from './reducers';

const store = createStore(reducers, {}, applyMiddleware(reduxThunk));
// ....

En index.js de src

Reglas Proxy (II)

  app.use(
    '/api/*',
    createProxyMiddleware({ 
      target: 'http://localhost:5000'
    })
  );  

1. En setupProxy.js agregar

2. Crear la siguiente estructura

3. En types.js

export const FETCH_USER = 'fetch_user';
// Para hacer peticiones ajax
import axios from 'axios';
import { FETCH_USER } from './types';

export const fetchUser = () => {
    axios.get('/api/current_user');
}

4. En index.js

Entender redux thunk (I)

Entender redux thunk (II)

// Para hacer peticiones ajax
import axios from "axios";
import { FETCH_USER } from "./types";

export const fetchUser = () => {
  return function(dispatch) {
    axios.get("/api/current_user").then(res =>
      dispatch({
        type: FETCH_USER,
        payload: res
      })
    );
  };
};

Refactoring

import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';

import Header from './Header';
const Dashboard = () => <h2>Dashboard</h2>;
const SurveyNew = () => <h2>SurveyNew</h2>;
const Landing = () => <h2>Landing</h2>;

class App extends Component {
    componentDidMount() {
        
    }
  
    render() {
        return (
            <div className="container">
                <BrowserRouter>
                    <div>
                        <Header />
                        <Route exact path="/" component={Landing} />
                        <Route exact path="/surveys" component={Dashboard} />
                        <Route path="/surveys/new" component={SurveyNew} />
                    </div>
                </BrowserRouter>
            </div>
        )
    };
    
}

export default App;

Probar fetchUser()

En App.js

import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
// Agregarmos estas importaciones para conectar la App con redux
import { connect } from 'react-redux';
import * as actions from '../actions';

// ...
    componentDidMount() {
	// Llamada al action
        this.props.fetchUser();
    }

// ...

// Pasamos los argumentos a App
export default connect(null, actions) (App);

En authReducer.js

export default function(state = {}, action) {
    console.log(action);
    switch(action.type) {
        default:
            return state;
    }
}

Refactoring

En actions/index.js

// Para hacer peticiones ajax
import axios from "axios";
import { FETCH_USER } from "./types";

export const fetchUser = () => async dispatch => {
  const res = await axios.get("/api/current_user");

  dispatch({ type: FETCH_USER, payload: res.data });
};

authReducer valores que retorna (I)

authReducer valores que retorna (II)

import { FETCH_USER } from '../actions/types';
export default function(state = null, action) {
    switch(action.type) {
        case FETCH_USER:
            return action.payload || false;
        default:
            return state;
    }
}

En authReducer.js

Acceder al estado desde el Header

import React, { Component } from 'react';
import { connect } from 'react-redux';

class Header extends Component {
    renderContent() {
        switch(this.props.auth) {
            case null:
                return 'Todavía decidiendo';
            case false:
                return 'Sessión cerrada';
            default: 
                return 'Sesión iniciada';
        }
    }
    render(){
        // console.log(this.props);
        return (
            <nav>
                <div className="nav-wrapper">
                <a href="#" className="brand-logo">mb-feedback</a>
                <ul id="nav-mobile" className="right hide-on-med-and-down">
                    { this.renderContent() }
                </ul>
                </div>
            </nav>
        );
    }
}

// function mapStateToProps(state) {
//     return { auth: state.auth };
// }
function mapStateToProps({ auth }) {
    return { auth };
}
export default connect(mapStateToProps)(Header);

En Header.js

Contenido del Header

// ...
renderContent() {
    switch (this.props.auth) {
      case null:
        return;
      case false:
        return <li><a href="/auth/google">Acceso con Google</a></li>;
      default:
        return <li><a href="">Salir</a></li>;
    }
  }

// ...

En Header.js

Redireccionar un usuario al autenticarse

// ...
app.get(
  '/auth/google/callback', 
  passport.authenticate('google'),
  (req, res) => {
    res.redirect('/surveys');
  }
);

// ...

Del lado del servidor authRoutes.js, modificar

Redireccionar al salir (I)

Redireccionar al salir (II)

// ...
renderContent() {
    switch (this.props.auth) {
      case null:
        return;
      case false:
        return <li><a href="/auth/google">Acceso con Google</a></li>;
      default:
        return <li><a href="/api/logout">Salir</a></li>;
    }
}

// ...

Del lado del cliente, en Header.js

Del lado del servidor, en authRoutes.js

// ...
app.get('/api/logout', (req, res) => {
  req.logout();
  res.redirect('/');
});
// ...

Componente Landing

import React from 'react';

const Landing = () => {
    return(
        <div style={{textAlign: 'center'}}>
            <h1>mb-feedback</h1>
            Obtener feedback de sus usuarios
        </div>
    );
};

export default Landing;

Del lado del cliente, en Landing.js

En App.js

// Reemplazar
const Landing = () => <h2>Landing</h2>;

// Por
import Landing from './Landing';
// Poner esto anter de las otras declaraciones para evitar error

Enlazar etiquetas (I)

En App.js

Enlazar etiquetas (II)

En Header.js

// ...
import { Link } from "react-router-dom";

// ...

render() {
    return (
      <nav>
        <div className="nav-wrapper">
          <Link 
            to={this.props.auth ? '/surveys' : '/'} 
            className="brand-logo"
          >
            mb-feedback
          </Link>
          <ul id="nav-mobile" className="right hide-on-med-and-down">
            {this.renderContent()}
          </ul>
        </div>
      </nav>
    );
  }

Pagos

Consideraciones para facturar

Reglas de facturación

Somos malos en seguridad

  • Nunca acepte números de tarjeta de crédito de forma directa
  • Nunca almacene números de tarjetas de crédito
  • Siempre use un procesador de pagos externo

La facturación es difícil

  • Evitar en lo posible pagos mensuales / planes múltiples
  • El fraude y las devoluciones de cargo son un dolor de cabeza

Consideraciones para facturar

Consideraciones para facturar

Reglas de facturación

Somos malos en seguridad

  • Nunca acepte números de tarjeta de crédito de forma directa
  • Nunca almacene números de tarjetas de crédito
  • Siempre use un procesador de pagos externo

La facturación es difícil

  • Evitar en lo posible pagos mensuales / planes múltiples
  • El fraude y las devoluciones de cargo son un dolor de cabeza

Explorar la API de Stripe

// Desde la terminal
cd client
npm install --save react-stripe-checkout

Instalar react-stripe-checkout 

Stripe API Keys

module.exports = {
    // ...
    stripePublishableKey: 'pk_test_maDckee4adUSBdCCQSefD3v100lI4N0eXi',
    stripeSecretKey: 'sk_test_NFUt0kfcFB0f8IMD39dG79FB00mqd8iN89'
}

En config/dev.js

module.exports = {
    // ...
    stripePublishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
    stripeSecretKey: process.env.STRIPE_SECRET_KEY
}

En config/prod.js

En heroku.com, agregar las variables al proyecto

Variables de entorno con React

REACT_APP_STRIPE_KEY=pk_test_maDckee4adUSBdCCQSefD3v100lI4N0eXi

.env.development

REACT_APP_STRIPE_KEY=pk_test_maDckee4adUSBdCCQSefD3v100lI4N0eXi

.env.production

// ...
console.log('STRIPE KEY ES', process.env.REACT_APP_STRIPE_KEY);
console.log('Eviroment es', process.env.NODE_ENV);

Para probar en index.js del client

El componente de pago

Payments.js

import React, { Component } from 'react';
import StripeCheckout from 'react-stripe-checkout';

class Payments extends Component {
    render() {
        debugger;
        return (
            <StripeCheckout 
                amount={500}
                token={token => console.log(token)}
                stripeKey={process.env.REACT_APP_STRIPE_KEY}
            />
        );
    }
} 

export default Payments;

En Header.js

// ...
import Payments from './Payments';

Token de Stripe

// ...

class Header extends Component {
  renderContent() {
    switch (this.props.auth) {
      case null:
        return;
      case false:
        return <li><a href="/auth/google">Acceso con Google</a></li>;
      default:
        return [
          <li key="1"><Payments /></li>,
          <li key="2"><a href="/api/logout">Salir</a></li>
        ];
    }
  }

// ...  

En Header.js

Pruebe a hacer un pago y vea en la consola el token y los resultados devueltos por Stripe

Ajustes varios a Pagos

import React, { Component } from 'react';
import StripeCheckout from 'react-stripe-checkout';

class Payments extends Component {
    render() {
        return (
            <StripeCheckout 
                name="mb-feeback"
                description="$5 para 5 créditos emails"
                amount={500}
                token={token => console.log(token)}
                stripeKey={process.env.REACT_APP_STRIPE_KEY}
            >
                <button className="btn">
                    Agregar Créditos
                </button>
            </StripeCheckout>
        );
    }
} 

export default Payments;

En Payments.js

Reusar acciones (actions) - I

Reusar acciones (actions) - II

En actions/index.js

// ...

export const handleToken = (token) => async dispatch => {
  const res = await axios.post("/api/stripe", token);

  dispatch({ type: FETCH_USER, payload: res.data });
};

Enviar el token de Stripe

En Payments.js

// ...
import { connect } from 'react-redux';
import * as actions from '../actions';

class Payments extends Component {
    render() {
        return (
            <StripeCheckout 
                name="mb-feeback"
                description="$5 para 5 créditos emails"
                amount={500}
                token={token => this.props.handleToken(token)}
                stripeKey={process.env.REACT_APP_STRIPE_KEY}
            >
                <button className="btn">
                    Agregar Créditos
                </button>
            </StripeCheckout>
        );
    }
} 

export default connect(null, actions)(Payments);

Manejador de la solicitud de Post del Token

En el server index.js

// ...
require('./routes/authRoutes')(app);
require('./routes/billingRoutes')(app);
// ...

En billingRoutes.js

module.exports = app => {
    app.post('/api/stripe', (req, res) => {
        
    });
}

Middleware bodyParser

Vamos a instalar desde el servidor una librería para Stripe que nos permite manejar los tokens que recibimos desde el front end y cargar ese monto en los créditos para emails: https://www.npmjs.com/package/stripe

// Desde el servidor
npm install --save stripe
npm install --save body-parser
// ...
const passport = require('passport');
const bodyParser = require('body-parser');
// ...

app.use(bodyParser.json());
app.use(
// ...

En index.js (server)

const keys = require('../config/keys');
const stripe = require('stripe')(keys.stripeSecretKey);

module.exports = app => {
    app.post('/api/stripe', (req, res) => {
      console.log(req.body);
    });
}

En billingRoutes.js (server)

Crear el objeto "charges"

const keys = require('../config/keys');
const stripe = require('stripe')(keys.stripeSecretKey);

module.exports = app => {
    app.post('/api/stripe', (req, res) => {
        stripe.charges.create({
            amount: 500,
            currency: 'usd',
            description: '$5 para 5 créditos emails',
            source: req.body.id
        });
    });
}

En billingRoutes.js

Finalizar "charges"

const keys = require('../config/keys');
const stripe = require('stripe')(keys.stripeSecretKey);

module.exports = app => {
    app.post('/api/stripe', async (req, res) => {
        const charge = await stripe.charges.create({
            amount: 500,
            currency: 'usd',
            description: '$5 para 5 créditos emails',
            source: req.body.id
        });

        console.log(charge);
    });
}

En billingRoutes.js

Agregar créditos a un usuario

// ...
const userSchema = new Schema({
    googleId: String,
    credits: { type: Number, default: 0 }
});

// ...

En models/User.js

// ...
module.exports = app => {
    app.post('/api/stripe', async (req, res) => {
        const charge = await stripe.charges.create({
            amount: 500,
            currency: 'usd',
            description: '$5 para 5 créditos emails',
            source: req.body.id
        });

        req.user.credits += 5;
        const user = await req.user.save();

        res.send(user);
    });
}

En billingRoutes.js

Requerir autenticación (I)

// ...
module.exports = app => {
    app.post('/api/stripe', async (req, res) => {
        if(!req.user){
            return res.status(401).send({ error: 'Debes hacer login' });
        }
    // ....

En billingRoutes.js

Lo anterior funciona, pero si queremos restringir otras rutas para que requieran autenticación tendríamos que copiar el código anterior en cada ruta, por lo que la solución viene en la siguiente presentación.

Requerir autenticación (II)

Vamos a crear un nuevo middleware para asegurarnos de que el usuario ha iniciado sesión

module.exports = (req, res, next) => {
    if(!req.user){
        return res.status(401).send({ error: 'Debes hacer login' });
    }
    // Si no hay problemas puede continuar
    next();
}

En requireLogin.js

Requerir autenticación (III)

Vamos a crear un nuevo middleware para asegurarnos de que el usuario ha iniciado sesión

module.exports = (req, res, next) => {
    if(!req.user){
        return res.status(401).send({ error: 'Debes hacer login' });
    }
    // Si no hay problemas puede continuar
    next();
}

En requireLogin.js

Requerir autenticación (IV)

const keys = require('../config/keys');
const stripe = require('stripe')(keys.stripeSecretKey);
const requireLogin = require('../middlewares/requireLogin');

module.exports = app => {
  // Observe que no hay llamada, express lo hará cuando haya una 
  // solicitud/request, puede haber tantos middleware como sea
  // necesario según la ruta
    app.post('/api/stripe', requireLogin, async (req, res) => {
        const charge = await stripe.charges.create({
            amount: 500,
            currency: 'usd',
            description: '$5 para 5 créditos emails',
            source: req.body.id
        });

        req.user.credits += 5;
        const user = await req.user.save();

        res.send(user);
    });
}

En billingRoutes.js

Mostrar cantidad de créditos

// ...
class Header extends Component {
  renderContent() {
    switch (this.props.auth) {
      case null:
        return;
      case false:
        return <li><a href="/auth/google">Acceso con Google</a></li>;
      default:
        return [
          <li key="1"><Payments /></li>,
          <li key='3' style={{ margin: '0 10px'}}>
            Credits: { this.props.auth.credits }
          </li>,
          <li key="2"><a href="/api/logout">Salir</a></li>
        ];
    }
  }
  
  // ...

En Header.js

Front end: rutas and producción

Mostrar cantidad de créditos

// ...
class Header extends Component {
  renderContent() {
    switch (this.props.auth) {
      case null:
        return;
      case false:
        return <li><a href="/auth/google">Acceso con Google</a></li>;
      default:
        return [
          <li key="1"><Payments /></li>,
          <li key='3' style={{ margin: '0 10px'}}>
            Credits: { this.props.auth.credits }
          </li>,
          <li key="2"><a href="/api/logout">Salir</a></li>
        ];
    }
  }
  
  // ...

En Header.js

Aplicaciones Web 2021

By Wilfredo Meneses

Aplicaciones Web 2021

  • 1,335