En este post vamos a desarrollar una api con sails versión 1.0, base de datos en mongodb y vamos almacenar las sesiones de los usuarios en redis con un jsonwebtoken, con esta configuración podemos hacer una api muy ágil capaz de recibir hasta 20mil a 60mil peticiones (es verdad) por minuto con un servidor de 2 GB de ram.
Desde hacer un tiempo he desarrollado varias apis con sails en mis primeras apis use sails auth o el waterline pero me generaba mucha basura en la base de datos y si habia mucho trafico la api se bloqueaba o incluso nunca contestaba las peticiones, pero aun asi segui utilizando sails pero ahora creando servicios que hicieran los querys a la base de datos de forma nativa y las sesiones (jsonwebtokens) los almacenaba en redis al estar en la memoria ram toma unos milisegundos validar la sesion y pasar al controller para realizar ciertas acciones.
1.- Instalación
Debemos instalar sails y crear nuestra aplicación con el siguiente comando
sails new api-login |
Instalamos las dependencias mongodb, redis, flat , dotenv y jsonwebtoken usamos el siguiente comando
npm install moment mongodb redis jsonwebtoken sails-mongo bcrypt flat dotenv jsonwebtoken --save |
2.- Variables de entorno
Es muy importante tener variables de entorno para esto creamos el archivo .env (recuerda agregar el archivo a .gitignore) por si queremos replicar nuestra api en otro proyecto solo cambiamos los valores como base de datos, url del domino , url de la api, correos electronicos entre otros para que toda nuestra api se adpate y no tengamos que modificar nuestro código.
Editarmos nuestras variables de entorno con la siguiente información:
PORT=1337 MAIN_DB_ADAPTER="sails-mongo" MAIN_DB_HOST="localhost" MAIN_DB_PORT="27017" MAIN_DB_DB="apilogin" TOKEN_KEY="rVkGZ0qixfNkkT1sJLrv" REDIS_PORT=6379 REDIS_HOST="localhost" REDIS_DB=1 DOMAIN_APP="https://herelodin.com" DOMAIN_API="http://api.herelodin.com/v1.0/" |
Donde indicamos el puerto en que se ejecutara nuestra aplicación (por defecto 1337) , el host de monogdb y el nombre de nuestra base de datos. tambien incluimos la conexion hacia redis.
Ahora en nuestro archivo app.js importamos nuestras variables de entorno justo en la primera linea
require('dotenv').config({ path: './.env' }); |
3.- Como configurar redis con SailsJs
Si usamos la versión 1.0 de sails creamos la carpeta services en la siguiente ruta /api/services y creamos el archivo RedisService.js con el que vamos a conectarnos a redis, obtener algun key o verificar que exista.
https://github.com/herel/api-login/blob/master/api/services/RedisService.js
Justo cuando arranca la aplicación debemos hacer la conexión con redis, por experiencia y evitar que por cada requests se realice una conexión saturando la memoria ram de nuestra aplicación. Debemos editar el archivo config/bootstrap.js https://github.com/herel/api-login/blob/master/config/bootstrap.js
module.exports.bootstrap = async function(done) { var client = RedisService.prepareConnect().createClient(process.env.REDIS_PORT, process.env.REDIS_HOST); client.on('connect',function(){ sails.log.debug('Redis connected'); client.select(process.env.REDIS_DB); RedisService.setConnection(client); return done(); }); }; |
Si ejecutamos nuestra aplicación con sails lift o node app.js veremos que imprime en la terminal “Redis connected”
4.- Como configurar Mongodb con SailsJs
Ahora debemos configurar la conexion a nuestra base de datos, podemos usar mongo, mysql, postgressql, pero en este tutorial lo vamos hacer con mongodb
previamente instalamos mongodb y sails mongo entonces vamos a nuestro archivo datastores.js en sails v1.0 o connections en las versiones menores a 1.0 y agregamos el siguiente codigo: https://github.com/herel/api-login/blob/master/config/datastores.js
mongodb:{ adapter : process.env.MAIN_DB_ADAPTER, host : process.env.MAIN_DB_HOST, port : process.env.MAIN_DB_PORT, database : process.env.MAIN_DB_DB } |
5.- Como configurar modelos con SailsJs
Vamos a crear el archivo models/User.js donde indicaremos la base de datos que queremos que se conecte para esto usamos el atributo datastore en sails 1.0 o connections en una versión menor. https://github.com/herel/api-login/blob/master/api/models/User.js
module.exports = { datastore: "mongodb" }; |
6.- ¿Debemos usar el ORM o blueprints de sails?
Despúes de implementar varias api´s notamos que al usar los blueprints o pupulate se vuelve lento o incluso con errores en nuestra base de datos si usamos el aftercreate o berforecreate, en algun punto realice una api que en las horas criticas recibia hasta 20 mil peticiones por minuto y usar el ORM y sails-auth cada vez que se hacia una peticion se genera un hash de cada sesion/petición en nuestra base de datos y teniamos una colleción con mas de 60 mil registros en un par de minutos causando que los requests se volvieran lentos incluso nunca respondiera la api.
Si su aplicación solo la usaran un par de usuarios es muy recomendable, pero si tienen en mente tener miles de usuarios, mi recomendación personal es crear servicios y de forma nativa consultar a mongo si existe un usuario, actualizar un usuario etc..
7.- Creamos nuestro UserService
Como mencione en el punto 6 lo ideal es crear servicios que se encargen de hacer ciertas acciones como crear un registro, actualizarlo, contar, o validar si existe ciertos atributos.
Vamos a crear un servicio llamada UserSerivce.js donde vamos a
- Crear el usuario con su contraseña encriptada
- Verificar si existe un usuario con su email
- Generar el hash para la contraseña
module.exports = { create : function(params,password){ //recuerda usar siempre promise se vera mas pro tu desarrollo ;) return new Promise(function(resolve, reject) { User.native(function(err, collection) { if(err) return reject({ error : true, message : "Internal server error" , status : 500 }); var query = { active : true, createdAt : new Date(), email : params.email, password : password } if(params.firstName) query.firstName = params.firstName; if(params.lastName) query.lastName = params.lastName; collection.insert(query,function done(err,result){ if(err) return reject({ error : true, message : "Internal server error", statis : 500 }); //todo salio bien :D regresamos el user que se inserto return resolve(result.ops[0]); }); }); }) } } |
Como pueden ver creamos una función que se llama create que recibe los parametros del usuario como firstName, lastName y la contraseña lo pasamos como segundo parametro ya que previamente debemos encriptrar para mayor seguridad, tambien mi recomendación es usar promise para y no callback con el tiempo notaran la diferencia y su codigo se vera mucho más limpio.
Si ocurre un error siempre mando un objeto que contiene el mensaje, el status en este caso el request responde con un error 500 y en nuestra app web o app mobile podamos validar si ocurre el error y asi evitamos que truene.
aqui el archivo completo : https://github.com/herel/api-login/blob/master/api/services/UserService.js
8.- Como configurar controller con SailsJs
Si hacemos un diagrama de flujo de como debe funcionar el crear un usuario seria de la siguiente forma:
Creamos nuestro controller que en este caso voy a llamarlo AccountController en la carpeta api/controllers/AccountController.js y creamos nuestro función create
function validEmail(email) { return /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test( email ); } module.exports = { create : function(req,res){ var params = req.params.all(); ... } } |
8.1.- Validar campos
Dedemos validar todos los campos para este login debemos hacer lo siguiente:
- Validar firstName que sea mayor a 4 caracteres
- Validar lastName que sea mayor a 4 caracteres
- Validar la contraseña que debe ser mayor a 6 caracteres
- Validar el correo electrónico que debe contener el nombre seguid de @ y el nombre de dominio.
module.exports = { create : function(req,res){ var params = req.params.all(); //validamos los campos if(!params.firstName || params.firstName.length <= 3) return res.send(401, { error : true, message : "El nombre es obligatorio o es muy corto", status : 401 }); if(!params.lastName || params.lastName.length <= 3) return res.send(401, { error : true, message : "El apellido es obligatorio o es muy corto", status : 401 }); if(!params.password || params.password.length <= 5) retunr res.send(401, { error : true, message : "la contraseña es muy corta", status : 401 }); // validamos el email if(!params.email || !validEmail(params.email)) return res.send(401, { error : true, message : "El correo es obligatorio ó no es valido", status : 401 }); } } |
8.2 validamos si el usuario ya existe
debemos evitar que el usuario lo insertemos nuevamente, podemos hacerlo de 2 formas:
- Agregar en mongo que el atributo email es unico y asi evitamos que se insertan dos veces
- Validar antes de insertar el usuario que el email no exista como se ve en el siguiente archivo https://github.com/herel/api-login/blob/master/api/controllers/AccountController.js
9.- Como configurar rutas con SailsJs
Como no vamos a usar el ORM o WaterLine de sails necesitamos crear manualmente las rutas de nuestra api.
por experiencia debemos crear un subdominio llamado api y contenga la versión de nuestra api por ejemplo http://api.herelodin.com/v1.0/ si creamos una App web o App mobile podemos conectarnos facilmente a nuestra api.
Creamos la ruta donde el usuario hace una petición de tipo POST por ejemplo POST api.domain.com/v1.0/account
para realizar este cambio vamos al archivo config/routes.js y agreamos la ruta asi: https://github.com/herel/api-login/blob/master/config/routes.js
'/': { view: 'pages/homepage' }, 'POST /v1.0/account' : 'AccountController.create', |
10.- Como hacer pruebas con PostMan a nuestra api en SailsJs
En la siguiente url http://api.herelodin.com/v1.0/ se encuntra el ejemplo de este post y pueden descargar el repositorio en github
10.1- Como realizar una petición post para registrar un usuario
para registrar un usuario creamos el endpoint http://api.herelodin.com/v1.0/account de tipo POST, si hacemos pruebas con POSTMAN obtenemos los siguientes resultados.
Si enviamos todos los datos correctamente la api debe desponder el usuario que acaba de insertar (recuerda siempre ocultar campos importantes como la contraseña)
Si hacemos la petición con los mismo parametros nos debe responder un error 401 indicando que el correo ya existe
Gracias por leer y compartir este tutorial, deja tus comentarios de que te ha parecido y si tienes dudas podemos resolverlas.
Tambien te recomiendo algunos libros que me han ayudado a mejorar las buenas practicas a la hora de programar.