¿Como programar una api en sailsJs para registrar usuarios con mongodb y jsonwebtokens?


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:

  1. Agregar en mongo que el atributo email es unico  y asi evitamos que se insertan dos veces
  2. 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.