Backend del proyecto AppSalon el cual será una web completa de citas y servicios de una peluquería. Tecnologías que usaremos es MEVN: MongoDb, Express, VueJs y Node.
Documentaré en este archivo el proceso de formación del proyecto para dejarlo como guía y recuerdo
- Creamos una carepta inicial "App_Salon", dentro "backend" y "frontend" , proyectos separados
- Dentro de backend, iniciamos proyecto node: npm init
- Creamos index.js
- Escrivimos scripts e instalamos nodemon: npm i -D nodemon (equivale al npm node --watch)
- ESM nomenclatura, añadimos en package.json, "type" : "module"
- Instalamos Express (servidor) : npm i express
- Escrivimos la parte inicial del código como ejemplo y probamos que funciona
import express from 'express' // ESM = Emma Script Modules
// 1 - Configurar la App - inicilizar servidor
const app = express()
// Definimos una ruta de tipo GET
app.get('/', (req, rest) => {
const producto = {
nombre: 'Jilbab Verde',
precio: '54',
cantidad: '10'
}
// rest.send(producto)
rest.json(producto)
})
// 3 - Definir puerto
const PORT = process.env.PORT || 4000
// Este codigo es para que asigne el hosting un puerto automaticamente o el 4000
// 4 - Arrancar la App
app.listen(PORT, () => {
console.log('El servidor se está ejecutando en el puerto: ',PORT)
})
El resultado es que en localhost:4000 veremos los datos del producto en formato JSON
- Seguidamente, creamos la carpeta /routes/servicesRoutes.js, el cual contiene la misma ruta, pero definimos aparte los servicios para hacer un código modular
- Creamos perfil MongoDB, obtenemos credenciales
- Instalamos Mongoose: npm i mongoose
- Creamos /config/db.js
import mongoose from 'mongoose'
export const db = async () => {
try{
const db = await mongoose.connect(process.env.MONGO_URI)
console.log("Conectados a la bd de MongoDB")
} catch (error) {
console.log(error)
process.exit(1) //Detenemos la ejecución si cae el servidor
}
}
- Lógicamente importamos db.json en index.js y llamamos la funcion db() (código en el repositorio)
- Instalamos la dependencia dotenv: npm i env
- Creamos archivo .env (lo añadimos a .gitignore)
- Definimos url privada -> MONGO_URI
- Inicializamos en index.js y ya podemos usar las variables secretas
- Instalamos colors: npm i colors (frikada para que quede chulo y organizadas las respuestas del server)
Creamos carpeta /models/Service.js es el nombre del modelo Service
import mongoose from 'mongoose'
const serviceSchema = mongoose.Schema({
nombre: {
type: String,
required: true,
trim: true,
},
precio: {
type: Number,
required: true,
trim: true,
}
})
const Services = mongoose.model('Services', serviceSchema)
export default Services
Con esto, tenemos disponible este schema para usarlo en todo el backend
- Instalamos postman para simular las request antes de realizarlas, sea de get, put, patch, delete, etc. Crear una collection para el proyecto y almacenas los distintos urls con el metodo adecuado
- Alternativa en VSC: Thunderclient (VSC Extension) - funciona muy bien
## Modelo, Schema y Controlador
Hemos creado carpeta /controllers/servicesController.js
- Este archivo es la que conecta con la db y devuelve al servicio servicesRoutes.js los datos que él debe devolver en formato json y responder a la request
// Codigo del controlador
import { services } from './../data/beautyServices.js' // Simula los datos que aun no estan en la db
const getServices = (req,rest) => {
rest.json(services)
}
export {
getServices
}
// codigo del servicio servicesRoutes.js
import express from 'express'
import { getServices } from '../controllers/servicesControllers.js'
const router = express.Router()
router.get('/', getServices)
export default router
- Extendemos todos los otros metodos del servicio: obtenerServicios, obtenerServicioById, updateServicio, deleteServicio
- Estamos ya interactuando con la bd simulando peticiones y es muy bonito, muy contento, el codigo del servicio es este:
- Enlace al archivo Lo indico así para no hacer este archivo demasiado extenso, el documento este es el controller que comunica entre el servicio y la bd
- Creamos /data/seeds.js con dos funciones seedDB() y clearDB() | La primera inserta todos los registros a la bd, datos los coge del archivo en data | La segunda elimina todos los registros de la bd
- Creamos dos scripts para esto, ejecuta cada una de las funciones
- "npm run seed-import" : "node data/seed.js --import"
- "npm run seed-export" : "node data/seed.js --export"
- Reescrivimos el metodo para obtener todos los servicios: services = await Services.find()
=======
Muy importante este paso para poder hacer peticiones desde el dominio (diferente) del frontend, esta es la configuración default
import cors from 'cors'
//Configuramos cors
const whiteList = [process.env.FRONTEND_URL, undefined]
const corsOptions = {
origin: function(origin, callback){
// origin es el dominio que está haciendo el request
if(whiteList.includes(origin) || !origin ){
//Permitimos conexion
callback(null, true)
} else{
// NO permitimos la conexion
}
}
}
app.use(cors(corsOptions))
undefined
Añadimos esto en la validación para que deje hacer peticiones desde Thunderclient o postman- origin en la funcion es el dominio desde el cual se hace la peticion
- whiteList es la lista de todos los dominios permitidos y comprobamos que el dominio desde el que hacemos la petición "incluya" el deseado para dar permiso
Detallaré aquí para mi propio uso las partes fundamentales para el sistema de logueado
- Los usuarios deben tener algo único que les identifique: nº targeta, dni, id, email, etc.
- La cuenta nueva debe ser siempre confirmada por el usuario, debemos crear el mecanismo para que el usuario confirme su nueva cuenta y evitar así el spam y reforzar la seguridad
- Autenticación:
- Email + Password
- Dispositivo: Token, Mensaje movil, 2FAS Auth
- Enlace mágico: usuario introduce email de login, le enviamos email con un enlace para darle acceso a la app durante unos minutos
- Huella / Cara
- Los passwords deben estar siempre hasheados (no se puede recuperar ni revertir la contraseña) por temas de seguridad y crear el algoritmo para que pueda recuperar la contraseña, escribiendo una nueva
import mongoose from 'mongoose'
import { uniqueId } from '../utils'
const userSchema = mongoose.Schema({
nombre: {
type: String,
required:true,
trim: true
},
password: {
type: String,
required:true,
trim: true
},
email: {
type: String,
required:true,
trim: true,
unique:true
},
token: {
type: String,
default: () => uniqueId()
},
verified: {
type: Boolean,
default: false
},
admin:{
type: Boolean,
default: false
}
})
const User = mongoose.model('User',userSchema)
export default User
- Definimos que el email sea único
- Creamos la función uniqueId() para crear un Id único para cada usuario
const uniqueId = () => Date.now().toString(32) + Math.random().toString(32).substring(2)
- Definimos un campo para ver si está verificado o no y otro para permitir el admin
- bcrypt, crypto-js... cogemos la más reciente
npm i bcrypt
En el modelo User.js, escribimos este código para que hashe la contraseña antes de crear el nuevo registro de usuario
userSchema.pre('save', async function(next){
if(!this.isModified('password')){
next()
}
const salt = await bcrypt.genSalt(10)
this.password = await bcrypt.hash(this.password, salt)
})
- Con "pre" indicamos que se debe ejecutar esta funcion antes de almacenar el registro
- La comprobación del if es para ver si ya ha sido hasheada la contraseña, no hacerlo dos veces
- salt es de la propia librería de bcrypt y crea un valor que se usa en el hasheo
- no hay forma de recuperar la contraseña inicial, pero sí que bcrypt ofrece la funcion .compare() para el login
- Usamos Mailtrap para usar correos de prueba, es gratis
- Nodemailer para integrar este servicio de email al proyecto node
- Creamos un archivo de configuracion con los datos que nodemailer nos ofrece y esta funcion la llamamos cada vez que queramos enviar un email, creamos un servicio para ello
import nodemailer from 'nodemailer'
export function createTransport(host, port, user, pass) {
const transporter = nodemailer.createTransport({
host,
port,
auth: {
user,
pass
}
});
return transporter
}