Vamos a crear un simple registro de usuarios con NodeJS, utilizaremos MySQL como DB y tambien vamos a usar Bootstrap.
Creamos primero y gracias a Express crearemos la estructura de nuestro proyecto.
1 |
express registroUsuarios |
Quedando la siguiente estructura (típica del framework Express):
Lo siguiente y como nos indica ya Express es instalar las dependencias necesarias y para ello:
1 |
cd registroUsuarios && npm install |
Ahora para ejecutar nuestra aplicación deberemos solo escribir:
1 |
DEBUG=registroUsuarios:* npm start |
Con esto ya tenemos corriendo nuestra app en el puerto por defecto y si ponemos http://localhost:3000 ya nos saldrá el mensaje tipico de Express “Wellcome to Express” pues se renderiza la vista index.jade.
Ahora eliminamos el archivo users.js que se encuentra en la carpeta routes y en esa misma carpeta renombramos index.js a routes.js.
Creamos la carpeta donde colocaremos los controladores:
1 |
mkdir controllers |
En el raíz editamos el archivo app.js eliminando todas las referencias a /routes/users y cambiando /routes/index por /routes/routes.
También creamos dentro de la carpeta de los controladores (controllers), nuestro primer controlador que sera index.js el cual se va a encargar de cargar todos los demás controladores y tenerlos cargados en todo momento. Con el siguiente código :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// cargamos el modulo de File System var fs = require('fs'); // cargamos el modulo de path var path = require ('path'); var files = fs.readdirSync(__dirname); files.forEach(function(file){ var fileName = path.basename(file, '.js'); if(fileName !== 'index'){ exports[fileName] = require('./'+ fileName); } }); |
Vamos tambien a crear un controller llamado HomeController.js que en principio tendrá:
1 2 3 4 5 6 |
module.exports = { index: function(req,res,next){ res.render('index'); } } |
Si ahora en las vistas cambiamos el texto por ejemplo, la vista index.jade su codigo dejamos este:
1 2 3 4 |
extends layout block content p= 'Sistema de login y registro con NodeJS' |
Y ya vemos que renderizara lo siguiente:
Vamos a utilizar Bootstrap en la parte del Front
Para ello vamos a realizar una serie de cambios creamos en las vistas una carpeta templates para tener todo un poco más ordenado y allí el template por defecto que es el que llevara cargado el Bootstrap quedando así (default.jade):
1 2 3 4 5 6 7 8 9 10 |
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css') script(scr="https://code.jquery.com/jquery-2.2.3.min.js") script(scr="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js") body block content |
Ahora dentro de las vistas eliminamos al index.jade renombrada a home.jade uyo contenido será:
1 2 3 4 |
extends templates/default block content h1= 'Pagina de Inicio' |
Y en el controller HomeController indicamos que renderice la vista home en lugar de la index que ya no existe.
Vamos a crear la conexión con nuestra base de datos para ello creamos el directorio “database” en el raíz del proyecto. Y dentro vamos a escribir la configuración de nuestra conexión con la base de datos:
Nuestro fichero se llamara config.js y contendrá:
1 2 3 4 5 6 7 8 9 |
var config ={ host : 'localhost', user : 'root', password :'debian2DAW', database : 'baseRegistroNodeJS' }; module.exports = config; |
Ahora instalamos en nuestro proyecto el modulo para MySQL de NodeJS. Utilizando npm:
1 |
npm install mysql |
Creo el controlador en la carpeta controllers llamado UserController.js:
1 2 3 4 5 6 7 8 9 |
var mysql = require('mysql'); module.exports ={ getSignUp : function(req,res,next){ return res.render('users/signup'); } }; |
Para probar que funciona este controlador creo una vista para los usuarios un signup.jade que tenga lo siguiente:
1 2 3 4 |
extends ../templates/default block content h3= 'Registrarme' |
Ahora en el fichero routes.js incluimos la route:
1 2 |
//routes de usuario router.get('/auth/signup', controllers.UserController.getSignUp); |
Y si ponemos localhost:3000/auth/signup saldrá por pantalla renderizada la vista con esto compruebo que está correctamente enrutado.
Ahora voy a crear el formulario completo de registro: (sigup.jade)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
extends ../templates/default block content div(class='row') div(class='col-md-6 col-md-offset-3') div(class='panel panel-default') div(class='panel-heading')= 'Registrarme' div(class='panel-body') form(action='http://localhost:3000/auth/signup' method='post' autocomplete='off') div(class='form-group') label(for="email")= 'Email' input(type='email' name='email' id='email' placeholder='Email' class='form-control') div(class='form-group') label(for="nombre")= 'Nombre' input(type='nombre' name='nombre' id='nombre' placeholder='Nombre' class='form-control') div(class='form-group') label(for="password")= 'Password' input(type='password' name='password' id='password' placeholder='Password' class='form-control') button(type="submit" class='btn btn-default')= 'Registrarme' |
Quedando así:
Tenemos que crear el método también en el controlador UserController.js:
1 2 3 4 |
postSignUp : function(req,res,next){ console.log(req.body); return; } |
Y tenemos que incluir la ruta en el fichero routes.js:
1 |
router.post('/auth/signup', controllers.UserController.postSignUp); |
Con esto ya podemos ver en el terminal del servidor que este recibe los datos del formulario:
Vamos a crear los campos de la base de datos MySQL:
1 2 3 4 5 6 7 8 |
use baseRegistroNodeJS; create table users( id int primary key auto_increment, email varchar(255) not null, nombre varchar(255) not null, password varchar(255) not null ); |
Y para el caso del password lo guardaremos como se debe encryptado para ello debemos instalar la library bcryptjs:
1 |
npm install bcryptjs |
Tambien incluire en el controlador dicha library:
1 |
var bcrypt = require('bcryptjs'); |
Modificamos el UserController.js para que al recibir por post los parámetros estos sean escritos en la DB:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
postSignUp : function(req,res,next){ var salt = bcrypt.genSaltSync(10); var password = bcrypt.hashSync(req.body.password, salt); var user = { email : req.body.email, nombre : req.body.nombre, password : password }; var config =require('.././database/config'); var db = mysql.createConnection(config); // conectamos la DB db.connect(); // insertamos los valores enviados desde el formulario db.query('INSERT INTO users SET ?', user, function(err, rows, fields){ if(err) throw err; db.end(); }); return; } |
Probamos y vemos que nos inserta correctamente los datos que introducimos en el formulario y el campo password lo inserta correctamente encriptado:
Vamos a crear el formulario de login, para ello en las vistas creamos una llamada signin.jade:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
extends ../templates/default block content div(class='row') div(class='col-md-6 col-md-offset-3') div(class='panel panel-default') div(class='panel-heading')= 'Iniciar Sesion' div(class='panel-body') form(action='http://localhost:3000/auth/signin' method='post' autocomplete='off') div(class='form-group') label(for="email")= 'Email' input(type='email' name='email' id='email' placeholder='Email' class='form-control') div(class='form-group') label(for="password")= 'Password' input(type='password' name='password' id='password' placeholder='Password' class='form-control') button(type="submit" class='btn btn-default')= 'Iniciar Sesion' |
Y tenemos ahora que incluir en routes.js la ruta nueva por get :
1 |
router.get('/auth/signin', controllers.UserController.getSignIn); |
Vemos la vista del inicio de sesion:
Podemos hacer que cuando acabe de registrarse vaya directamente a la página de inicio de sesion. Para esto cambiamos el return en el método postSignUp poniendo:
1 |
return res.redirect('/auth/signin'); |
Creación y uso de mensajes Flash
Los mensajes Flash sólo aparecerán una vez. Sirven para advertir de un acción realizada a un usuario en un momento concreto pasado el mismo el mensaje dejará de aparecer. Para esto debemos instalar en nuestro proyecto varias librerías:
1 |
npm install connect-flash |
y también
1 |
npm install express-session |
ahora vamos a cargarlas para poder ser usadas en nuestro proyecto y para ello añadimos en app.js dos lineas:
1 2 |
var flash = require('connect-flash'); var session = require ('express-session'); |
Y vamos a configurar viendo que en el caso de session hemos utilizado parámetros de configuración que se pueden ver que significan en la documentación:
1 2 3 4 5 6 |
app.use(session({ secret: 'claveSecretaIDQ', resave: false, saveUninitialized: false })); app.use(flash()); |
Ahora vamos a nuestro controlador y en nuestro método de registro de usuarios vamos a poner un mensaje flash que advierta al usuario que se registro correctamente:
1 |
req.flash('info', 'Se ha registrado correctamente ya puede iniciar session'); |
Y vamos a pasar a la vista el valor de esa variable flash que hemos creado y a la cual llamamos info :
1 |
return res.render('users/signin', {message:req.flash('info')}); |
Y ahora en la vista donde queremos que salga nuestro mensaje ponemos:
1 2 |
if message.length > 0 div(class='alert alert-success' role='alert') = message |
Logearse para un usuario ya registrado
Para ello lo primero que hacemos es instalar una nueva dependencia:
1 |
npm install passport |
En este caso como no vamos a utilizar una api externa para el logeo nuestra estrategia va a ser logearse de forma local, instalaremos:
1 |
npm install passport-local |
Vamos a continuación a app.js a registrar la library:
1 |
var passport = require ('passport'); |
Y en este caso en el mismo archivo app.js colocaremos dos lineas de middleware:
app.use(passport.initialize());
app.use(passport.session());
Ahora en el raíz del proyecto creamos una nueva carpeta llamada passport y dentro passport.js. Este ultimo archivo es el que se encargara de autenticar a nuestro usuario cuando se identifique o haga login:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var LocalStrategy = require('passport-local').Strategy; module.exports = function(passport){ passport.serializeUser(function(user, done){ done(null, user); }); passport.deserializeUser(function(obj,done){ done(null,obj); }); passport.use(new LocalStrategy({ passReqToCallback: true }, function(req,email,password,done){ console.log(email); return; } )) }; |
Ahora vamos al fichero routes.js para colocar las rutas de cuando el login sea correcto a donde se ir y cuando sea incorrecto también:
1 2 3 4 5 |
router.post('/auth/signin', passport.authenticate('local',{ successRedirect: '/', failureRedirect:'/auth/signin', failureFlash: true })); |
No hay que olvidar requerir en app.js el archivo que hemos creado passport.js y eso se hace incluyendo en el mismo las líneas:
1 |
require('./passport/passport')(passport); |
Ahora en las vistas en nuestro formulario signin.jade , es MUY importante que el campo de email que se usara para login se llame username, para ello quedará así:
1 2 |
label(for="email")= 'Email' input(type='email' name='username' id='email' placeholder='Email' class='form-control') |
Comprobamos que desde el navegador al ir a identificarme el email que introduzco es recibido por el servidor. Una vez comprobado esto podemos pasar a modificar el fichero password.js para que se conecte a la base de datos y compruebe si es correcto o no el email y password introducidos con los existentes en nuestra base de datos:
1 2 |
var mysql= require('mysql'); var bcrypt = require('bcryptjs'); |
Una vez incluidas esas librarys modificamos :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
passport.use(new LocalStrategy({ passReqToCallback: true }, function(req,email,password,done){ var config = require('.././database/config'); // nos conectamos a la base de datos var db = mysql.createConnection(config); db.connect(); db.query('SELECT * FROM users WHERE email = ?',email, function(err,rows,fields){ if(err) throw err; db.end(); if(rows.length > 0){ var user = rows[0]; if (bcrypt.compareSync(password,user.password)) { return done (null,{ id: user.id, nombre: user.nombre, email: user.email }); } } return done(null,false, req.flash('authmessage','Email o Password incorrecto')); }); } )); |
Para probar que todo es correcto vamos primero a modificar el HomeController.js :
module.exports = {
index: function(req,res,next){
res.render(‘home’,{
isAuthenticated : req.isAuthenticated(),
user : req.user
});
}
}
Con esto cuando se renderice la Home mandara un JSON con dos parametros uno para saber si esta autentificado isAuthenticated y como segundo parámetro el usuario que esta autentificado user . Cambio la vista de la home que es home.jade quedando:
1 2 3 4 5 6 |
extends templates/default block content h1= 'Pagina de Inicio' if isAuthenticated p='Bienvenido '+ user.nombre |
Ahora si me logeo y voy a la home sale:
Para crear el LOGOUT vamos a crear en nuestro controlador UserController.js una nueva función que llamaremos logout, en un alarde de imaginación XDD :
1 2 3 4 5 |
logout : function(res,req,next){ //esta es una llamada a la funcion logout de passport req.logout(); res.redirect('/auth/signin'); } |
Y ahora creamos la ruta añadiendola en routes.js :
1 |
router.get('/auth/logout', controllers.UserController.logout); |
Y en la vista de nuestra barra de navegación nav.jade ponemos :
1 |
a(href="http://localhost:3000/auth/logout")= 'Cerrar Sesion' |
Ahora vamos a crear la VISTA del panel de usuario así que en nuestras vistas ponemos un nuevo archivo llamado panel.jade:
1 2 3 4 5 |
extends ../templates/default block content p='Hola .....'+user.nombre |
Y creamos nuestra funcion en el controlador UsersController.js:
1 2 3 4 5 6 |
getUserPanel : function(req,res,next){ res.render('users/panel',{ isAuthenticated : req.isAuthenticated(), user : req.user }); } |
Creamos también la route en routes.js:
1 |
router.get('/users/panel', controllers.UserController.getUserPanel); |
En la vista debemos colocar ahora la ruta en nav.jade:
1 |
a(href="http://localhost:3000/users/panel")= 'Perfil' |
Y en routes.js ahora al logearnos vamos a redireccionar a nuestro panel de control, por esto debemos cambiar:
1 2 3 4 5 |
router.post('/auth/signin', passport.authenticate('local',{ successRedirect: '/users/panel', failureRedirect:'/auth/signin', failureFlash: true }) |
Compruebo que todo funciona de forma correcta y vamos a hacer unos cambios de funcionalidad desde el punto de vista del usuario. Cuando NO este logeado solo se verán las opciones de Registrase o Iniciar Sesion. Y cuando el usuario SI este logeado no se verán esas opciones y si la de Cerrar Sesion y Perfil. Para esto vamos a cambiar la VISTA de la barra de navegación nav.jade añadiendo dos condicionales que mostrarán el codigo si estamos autentificados o NO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if !isAuthenticated li a(href="http://localhost:3000/auth/signup")= 'Registrarse' li a(href="http://localhost:3000/auth/signin")= 'Iniciar Sesion' if isAuthenticated li(class="dropdown") a(href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false")= user.nombre span(class="caret") ul(class="dropdown-menu") li a(href="http://localhost:3000/users/panel")= 'Perfil' li a(href="http://localhost:3000/auth/logout")= 'Cerrar Sesion' |
Problema de seguridad : si un usuario escribe en el navegador la ruta del panel de control /users/panel tanto si ESTA o NO logeado puede acceder a esa ruta y nosotros NO queremos que esto suceda.
Para evitar esto vamos a crear un middleware que va a comprobar si el usuario esta logeado o NO y en función de como este PERMITIRA o NO PERMITIRA acceder a esa ruta.
Creamos una carpeta llamada middleware y dentro de ella un middleware llamado auth.js (estara entre el require y response) y su código será el siguiente:
1 2 3 4 5 6 7 8 9 |
module.exports ={ isLogged:function(req,res,next){ if(req.isAuthenticated()){ next(); }else{ res.redirect('/auth/signin'); } } } |
Y el middleware lo colocaremos en todas aquellas rutas que queramos que se realice esta comprobación para ello modificamos en routes.js :
Introduciendo el require a nuestro middleware
1 |
var AuthMiddleware = require('.././middleware/auth'); |
Y ahora en este mismo archivo todas las rutas que queramos proteger entre el req y el res pondremos nuestro middleware en nuestro caso AuthMiddleware.isLogged, por ejemplo al panel de control:
1 |
router.get('/users/panel', AuthMiddleware.isLogged ,controllers.UserController.getUserPanel); |
Por ultimo vamos a hacer un menaje para cuando el password o login son incorrectos. Para ello solamente tendremos que tocar en el controlador UserController.js y colocar el mensaje en la vista del formulario correspondiente. En concreto en UserController.js solamente vamos a cambiar :
1 2 3 |
getSignIn : function(req,res,next){ return res.render('users/signin', {message:req.flash('info'), authmessage: req.flash('authmessage')}); } |
Y en la vista signin.jade vamos a poner el mensaje de error en la parte inferior del formulario:
1 2 |
if authmessage.length > 0 div(class='alert alert-danger' role='alert')= authmessage |
Si todo está correcto cuando vayamos a logearnos con un usuario o contraseña incorrectos nos saldrá un mensaje:
Se puede aun perfeccionar mucho el código así como los mensajes de error para indicar de una forma más precisa al usuario que estuvo mal, pero con esto que es lo básico podemos ver como hacer este registro de usuarios.
El código completo esta en : https://github.com/idqweb/registroNodeJS
Hola que tal, buena tarde.
Una pregunta, intente ejecutar la aplicación y funciona perfecto, pero al momento de tratar de visualizarla desde un dispositivo móvil por medio de ip al proyecto, (ejem: x.x.x.x:3000/auth/signin) lo hace perfecto pero no me redirecciona correctamente a “/users/panel” ya que cambia la ip por “localhost” no se si se deba a “passport”, saludos, muchas gracias
Buenas Germán,
Por lo que me comentas, parece que en algún lugar del codigo dejaste localhost y por eso no usa tu ip.
Sino debería funcionar bien.
Comentame si lo solucionaste en caso contrario, buscamos el fallo o porque no te fnciona de forma perfecta.
Un saludo,
Buenas… la parte de creacion de la base de datos… no esta muy clara, no consigo pasar de ahi.
No se crearla, he tratado de usar wamp para tner una,pero no funciona…
Alguna idea?
Buenas Jose Pablo,
Por lo que comentas creo utilizas windows como sistema operativo al decir que usas wamp. En esta instalación se uso linux como sistema operativo.
Para esta instalación solamente sería necesario instalar https://dev.mysql.com/downloads/installer/.
Pero vamos por partes:
1º) Descargarte babun que es un shell muy interesante y sobre el que podrás ejecutar ordenes de Linux. Puedes hacerlo https://babun.github.io/
2º) Instala mysql y accede a ella. Hay muchos manuales que enseñan como hacer dicha instalación.
3º) Aquí lo unico que se hace es configurar un conector entre nodejs y mysql.
Cualquier duda. Pregúntame e intentaré ayudarte.
Un saludo.
Buenas, buen ejemplo y muy bien explicado, veo que tú lo trabajas con Jade, es posible realizar todo esto con ejs?
Saludos!!
Si utilizando ejs que es otro templating language, tambien se podría hacer.
Si te atreves a intentarlo, publicamos la parte del Front-end con esa otra opción.
Un saludo.