📜 ⬆️ ⬇️

Learning Node.js from start to finish in practice. Part 1

Prehistory


There is a huge amount of various documentation on Node.js, there are plenty of ready-made solutions too, but starting to write a website you run into the problem: “Where do you start?”. I want to tell you my experience of studying Node.js in practice. The task is quite simple and clear - GPS Tracker with an Internet service, displaying our transmitters on a map, drawing a route of movement, etc., how much imagination is spreading. The project is not commercial and is written for the benefit of humanity for themselves.

Arranging workplace


I have to work both at work and at home, the work is neither related to site-building and this should not interfere with the working process. On this, choosing IDE, the choice fell on Cloud9 IDE . Houses used for convenience WebStorm . All site data needs to be stored somewhere, after studying the theoretical part , it was decided in practice to get acquainted with this type of database. In order not to become attached to the workplace, MongoDB and free hosting for the base on mongohq.com were chosen as the DBMS.
So, we have an empty project and an empty base. You can proceed.
1. Web application framework, one of the most common is express .
2. It was decided to write the site entirely in HTML5, so the EJS was chosen for this template engine.
3. MongoDB driver, there are a number of them, but my choice was mongodb .
4. Verification of user-entered data, node-validator .

Site structure


 BigBrother - \
       - controllers (all objects for working with the database)
       - public - \ (statically files (css, js, images))
             - css
             - js
             - images
       - routers (site routes)
       - views (page templates)
       - config.js (settings, for example, connections to the database)
       - server.js (the server itself)

First step: Authorization


The material for the study served as a topic and the task was typical, to enable users to register, recover the password, remember the login on the site, monitor the mail and the entry itself.

1. Site face

Idea: a button in the corner of the screen, click on it, a modal window appears with the possibility to register / enter the site with partial verification of the entered data (further you can complicate the verification).
Style sheet style.css
@import url(http://fonts.googleapis.com/css?family=Tenor+Sans&subset=latin,cyrillic); body{ font-family: 'Tenor Sans', sans-serif; } #mainmap{ width : 100%; } #topmenu{ width: 100%; height: 80px; background-color: white; } #topmenu #user{ background-color: rgb(228, 228, 228); cursor: pointer; position: absolute; top: 20px; right: 20px; vertical-align: middle; text-align: center; padding: 10px; border: 1px solid gray; } #topmenu #user:hover{ background-color: rgb(188, 188, 188); border: 1px solid gray; } .window{ position: fixed; top: 0px; left: 0px; width: 100%; height: 100%; background-color: rgba(40,40,40,0.5); z-index: 9999; color: rgb(80,80,80); display: none; } .window .back{ position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; z-index: 0; } .window .wrap{ position: fixed; width: 500px; height: 400px; top: 50%; left: 50%; margin: -200px -250px; background-color: white; border: 1px solid silver; padding: 10px; z-index: 1; } .window .wrap .header{ font-size: 25px; color: rgb(40,40,40); width: 100%; border-bottom: 1px solid gray; padding-bottom: 5px; } .window .wrap .header .active{ color: rgb(40,90,40); background-color: rgb(220,200,200); } .window .wrap .header div{ display: inline; cursor: pointer; background-color: rgb(230,240,240); padding: 2px; } .window .wrap .header div:hover{ color: rgb(40,40,90); background-color: rgb(230,230,230); } .window .wrap .msg{ display: none; position: absolute; top: 41px; left: 10px; width: 480px; background-color: rgb(220,100,100); padding: 10px; color: black; } .window .wrap .line{ margin-top: 10px; margin-left: 50px; } .window .wrap .line .label{ font-size: 20px; } .window .wrap .line .edit input[type='text'], .window .wrap .line .edit input[type='email'], .window .wrap .line .edit input[type='password']{ width: 400px; height: 25px; margin-top: 5px; border: 1px solid silver; font-size: 20px; } .window .wrap .line .edit input[type='text'], .window .wrap .line .edit input[type='email'], .window .wrap .line .edit input[type='password']:focus{ border: 1px solid gray; } .window .wrap .line .edit .error{ border-color: red; } .window .wrap .buttons{ position: absolute; width: 100%; height: 40px; left: 0; bottom: 0; background-color: rgb(240,240,240); color: rgb(40,40,40); } .window .wrap .buttons .button{ float: right; padding: 5px; border: 1px solid gray; margin: 5px; cursor: pointer; } .window .wrap .buttons .button:hover{ background-color: rgb(188, 188, 188); border: 1px solid gray; } 


Home index.ejs
  <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" > <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" > <!-- CSS !--> <link rel="stylesheet" href="css/reset.css"> <link rel="stylesheet" href="css/style.css"> <!-- Utils !--> <script src="http://yandex.st/jquery/1.8.2/jquery.min.js"></script> <script src="js/jquery.cookie.js"></script> <script src="js/core.js"></script> <title>BigBrother - <%= title %></title> </head> <body> <div id="topmenu"> <div id="user"> Login </div> </div> <div class="window" id="login"> <div class="wrap"> <div class="header"> <div id="pagelogin" class="active">Login</div> / <div id="pageregister" class="">Registration</div> </div> <div class="line" style="margin-top: 80px"> <div class="label">Email:</div> <div class="edit"><input type="email" id="email"/></div> </div> <div class="line" style="margin-top: 10px"> <div class="label">Password:</div> <div class="edit"><input type="password" id="password"/></div> </div> <div class="line" style="margin-top: 10px" id="confirmationpassworddiv"> <div class="label">Confirmation password:</div> <div class="edit"><input type="password" id="confirmationpassword"/></div> </div> <div class="line" style="margin-top: 10px"> <div class="label">Stay online:</div> <div class="edit"><input type="checkbox" id="stayonline"/></div> </div> <div class="buttons"> <div class="button" id="logincancel">cancel</div> <div class="button" id="loginsbmt">login</div> </div> <div class="msg"> </div> </div> <div class="back"></div> </div> <script type="text/javascript"> jQuery(window).load(function(){ Init(); }); </script> </body> </html> 


Events and core.js Handlers
 function Init(){ /* LOGIN WINDOW */ //Init vars var user = jQuery('#user'); var loginwindow = jQuery('#login'); var loginemail = jQuery('#email'); var loginpassword = jQuery('#password'); var confirmationpassword = jQuery('#confirmationpassword'); var pagelogin = jQuery('#pagelogin'); var pageregister = jQuery('#pageregister'); var confirmationpassworddiv = jQuery('#confirmationpassworddiv'); var loginmsg = jQuery(jQuery('.msg',loginwindow)[0]); var stayonline = jQuery('#stayonline'); confirmationpassworddiv.hide(); loginmsg.hide(); //Set events confirmationpassword.keypress(inputkeypress); loginpassword.keypress(inputkeypress); loginemail.keypress(inputkeypress); user.click(function(){ loginwindow.fadeIn('fast'); }); jQuery('#logincancel').click(function(){ hidewindow(); }); jQuery('#loginsbmt').click(function(){ var isLogin = pagelogin.hasClass('active'); var error = false; var errormsg = ''; if (loginemail.val()===''){ error = true; loginemail.addClass('error'); errormsg += 'Type your email'; }else loginemail.removeClass('error'); if (!isLogin){ if (loginpassword.val()===''){ error = true; loginpassword.addClass('error'); errormsg += '<br/>Type your password'; }else loginpassword.removeClass('error'); if (confirmationpassword.val()===''){ error = true; confirmationpassword.addClass('error'); errormsg += '<br/>Type your confirmation password'; }else confirmationpassword.removeClass('error'); if (confirmationpassword.val()!=loginpassword.val()){ error = true; confirmationpassword.addClass('error'); errormsg += '<br/>Password not same'; } } if (!error) { if (!isLogin) registeruser(); else loginuser(); }else{ loginmsg.html(errormsg); loginmsg.show(); } }); jQuery('.back',loginwindow).click(function(){ hidewindow(); }); pagelogin.click(function(){ pagelogin.addClass('active'); pageregister.removeClass('active'); confirmationpassworddiv.hide(); loginmsg.hide(); jQuery('#loginsbmt').html('login'); }); pageregister.click(function(){ pagelogin.removeClass('active'); pageregister.addClass('active'); confirmationpassworddiv.show(); loginmsg.hide(); jQuery('#loginsbmt').html('register'); }); //Check login state checklogin(); //Other function function inputkeypress(){ if (jQuery(this).val()!=='') jQuery(this).removeClass('error'); } //Hide login window and clear state function hidewindow(){ loginwindow.fadeOut('fast',function(){ loginmsg.hide(); confirmationpassword.removeClass('error'); loginpassword.removeClass('error'); loginemail.removeClass('error'); pagelogin.click(); }); } //Do login function loginuser(){ jQuery.ajax({ type: "POST", url: "/auth", data: { email: loginemail.val(), password: loginpassword.val(), stayonline: stayonline.val()==='1'} }).done(function( msg ) { loginpassword.val(''); if (msg.error){ loginmsg.html(msg.msg); loginmsg.show(); }else{ jQuery.cookie('sessionid',msg.sessionid); loginmsg.html(msg.msg); loginmsg.show(); setTimeout(function() { hidewindow(); checklogin(); }, 1000); } }); } //Check login state function checklogin(){ user.html('loading...'); jQuery.ajax({ type: "GET", url: "/auth" }).done(function( msg ) { if (msg.error) { user.html('login'); user.unbind('click'); user.click(function(){loginwindow.fadeIn('fast');}); } else { user.html(msg.displayname); user.unbind('click'); user.click(logout); } }); } //Do log out function logout(){ jQuery.ajax({ type: "DELETE", url: "/auth" }).done(function(msg){ if (msg.error){ alert(msg.msg); return; } user.html('login'); user.unbind('click'); user.click(function(){ loginwindow.fadeIn('fast'); }); }); } //Register new user function registeruser(){ jQuery.ajax({ type: "POST", url: "/auth/register", data: { email: loginemail.val(), password: loginpassword.val(), confirmationpassword: confirmationpassword.val(), stayonline: stayonline.val()==='1'} }).done(function( msg ) { loginpassword.val(''); confirmationpassword.val(''); if (msg.error){ loginmsg.html(msg.msg); loginmsg.show(); }else{ loginmsg.html(msg.msg); loginmsg.show(); setTimeout(function() { hidewindow(); checklogin(); }, 1000); } }); } /* LOGIN WINDOW */ }; 


')
2. Server part

Day start create a database controller. Its task is to connect to the database, plus some more frequently used procedures will be stored there, /controllers/db.js :
 exports.opendb = function(settings, callback){ var mongo = require('mongodb'), Server = mongo.Server, Db = mongo.Db; var server = new Server(settings.host, settings.port, {auto_reconnect: settings.auto_reconnect}); var db = new Db(settings.db, server); db.open(function(err, db) { if(!err) { db.authenticate(settings.username, settings.password, function(){callback(false, db);}); } else callback(true, db); }); }; exports.criptpassword = function(string){ var crypto = require('crypto'); return crypto.createHash('md5').update(string+global.saldo).digest("hex"); }; 

In order not to produce a huge bunch of variables when working, let's create an index.js file in the controllers folder. In this case, if we write
 global.controllers = require('./controllers'); 

our index.js will be included in the variable:
 exports.db = require('./db'); exports.users = require('./users'); exports.stayonlinesessions = require('./stayonlinesessions'); 

The next step is to create a router for the start page / routers/index.js
 exports.index = function(req, res){ res.render('index',{title: 'Home'}); }; exports.auth = require('./auth'); 

We will store user sessions in our database, use the connect-mongo package for this.
And actually our server.js .
We initialize global modules and variables
 //Modules var express = require("express"); var app = express(); var MongoStore = require('connect-mongo')(express); var dbsettings = require('./config').settings; //Server configuration global.saldo = 'fewfwef352tFRWEQF'; global.controllers = require('./controllers'); //Controllers var routers = require('./routers'); //Routers var viewEngine = 'ejs'; 

We configure the express server
 // Configuration app.configure(function(){ app.set('views', __dirname + '/views'); app.set('view engine', viewEngine); app.use(express.cookieParser()); app.use(express.session({ secret: 'fegwegwe', store: new MongoStore(dbsettings), cookie: { path: '/', httpOnly: true, maxAge: 1000*60*60*24 } })); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(__dirname + '/public')); }); 

There is a small digression and difficulty I encountered. If the initialization of the sessions is specified after the determination of the routes, then the sessions for some reason do not work, we initialize for this session earlier.
Right

 // Configuration app.configure(function(){ app.set('views', __dirname + '/views'); app.set('view engine', viewEngine); /*---------------------------------------------------*/ app.use(express.cookieParser()); app.use(express.session({ secret: 'fegwegwe', store: new MongoStore(dbsettings), cookie: { path: '/', httpOnly: true, maxAge: 1000*60*60*24 } })); /*---------------------------------------------------*/ app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(__dirname + '/public')); }); 


Wrong

 // Configuration app.configure(function(){ app.set('views', __dirname + '/views'); app.set('view engine', viewEngine); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(__dirname + '/public')); /*---------------------------------------------------*/ app.use(express.cookieParser()); app.use(express.session({ secret: 'fegwegwe', store: new MongoStore(dbsettings), cookie: { path: '/', httpOnly: true, maxAge: 1000*60*60*24 } })); /*---------------------------------------------------*/ }); 

Customize routes
 app.get('/',routers.index); //  app.post('/auth/register',routers.auth.register); //  app.post('/auth',routers.auth.login); //   app.get('/auth',routers.auth.getlogin); //  :    app.del('/auth',routers.auth.logout); //   

Connect to the database, if successful, start the server
 //Connect to db and start global.controllers.db.opendb(dbsettings, function(error,db){ if (!error){ global.db = db; app.listen(process.env.PORT); } else console.log('Error connect to db'); }); 

We try to start, we look at the main page, it works.
Now we describe the controller responsible for site users: /controllers/users.js
User registration:
 exports.register = function(email, password, callback){ global.db.collection('users', function(err, collection) { if (err){ callback(true); return; } collection.findOne({email: email.toLowerCase()}, function(eror, item){ if (item === null){ collection.insert({email: email.toLowerCase(), password: global.controllers.db.criptpassword(password), emailchack: true, roles: []},{safe:true},function(error, result){ if (err){ callback(true); return; } callback(false, result[0]); }); }else callback(true, null, 'User already exists'); }); }); }; 

Get user by ID
 exports.getuser = function(id, callback){ global.db.collection('users', function(err, collection) { if (err){ callback(true); return; } var ObjectID = require('mongodb').ObjectID; if (typeof id === 'string') id = new ObjectID(id); collection.findOne({_id: id}, function(eror, item){ if (err){ callback(true); return; } callback(false, item); }); }); }; 

Check whether it is possible to enter the user
 exports.checkuser = function(email, password, callback){ global.db.collection('users', function(err, collection) { if (err){ callback(true); return; } collection.findOne({email: email.toLowerCase(), password: global.controllers.db.criptpassword(password)}, function(error,item){ if (item === null) item = {}; callback(error, item.emailchack, item); }); }); }; 

If the user ticks the “remember me” checkbox, we will add the hashed user ID to the cookie and store everything in the “stayonlinesessions” document controller. Write the controller responsible for the session /controllers/stayonlinesessions.js
  exports.savesession = function(user_id, callback){ global.db.collection('stayonlinesessions', function(err, collection) { if (err){ callback(true); return; } var hash = global.controllers.db.criptpassword(user_id.toString()); collection.insert({user_id: user_id, hash: hash, createdate: new Date()},{safe:true},function(error, result){ if (err){ callback(true); return; } callback(false, hash); }); }); }; exports.getsession = function(hash, callback){ global.db.collection('stayonlinesessions', function(err, collection) { if (err){ callback(true); return; } collection.findOne({hash: hash}, function(error,item){ if (err || item === null){ callback(true); return; } global.controllers.users.getuser(item.user_id, function(error, user){ if (err || user === null){ callback(true); return; } callback(false, user); }); }); }); }; exports.delsession = function(hash, callback){ global.db.collection('stayonlinesessions', function(err, collection) { if (err){ callback(true); return; } collection.remove({hash: hash}, {safe: true}, function(err,removed){ if (err || !removed){ callback(true); return; } callback(false); }); }); }; 

So, we have controllers that are responsible for registration and sessions, now we need to implement authorization routes. We will be responsible for these functions / routers/auth.js .
User registration
 exports.register = function(req, res){ var email = req.body.email; var password = req.body.password; var confirmationpassword = req.body.confirmationpassword; var stayonline = req.body.stayonline; //   var check = require('validator').check; if (!check(email).len(6, 64).isEmail() || !check(password).notNull() || !check(password).equals(confirmationpassword)){ res.send({ error: true, msg: 'Check your' }); return; } //   global.controllers.users.register(email, password, function(error, user, msg){ if (error){ if (typeof msg == 'undefined' || msg===null ) msg = 'Register error'; res.send({ error: true, msg: msg }); return; } //  req.session.authorized = true; req.session.user_id = user._id; req.session.username = user.email; //   " "        if (stayonline){ global.controllers.stayonlinesessions.savesession(user._id, function(error, hash){ res.send({ error: false, msg: 'Success register email: '+email, sessionid: hash }); }); }else{ res.send({ error: false, msg: 'Success register email: '+email }); } }); }; 

Sign in
 exports.login = function(req, res){ var email = req.body.email; var password = req.body.password; var stayonline = req.body.stayonline; global.controllers.users.checkuser(email, password, function(error, canlogin, user){ if (error || !canlogin){ res.send({ error: true, msg: 'Check your email or password' }); return; } req.session.authorized = true; req.session.user_id = user._id; req.session.username = user.email; if (stayonline){ global.controllers.stayonlinesessions.savesession(user._id, function(error, hash){ res.send({ error: false, msg: 'Success login email: '+user.email, sessionid: hash }); }); } else { res.send({ error: false, msg: 'Success login email: '+user.email }); } }); }; 

Logout
 exports.logout = function(req, res){ if (!req.session.authorized){ res.send({ error: true, msg: 'You are not loggined' }); return; } req.session.authorized = false; delete req.session.username; delete req.session.user_id; //     ,    if (typeof req.cookies.sessionid !== 'undefined' && req.cookies.sessionid !== ''){ global.controllers.stayonlinesessions.delsession(req.cookies.sessionid, function(error){ if (error){ console.log('Session was not deleted'); return; } }); } res.send({ error: false }); }; 

Getting current authorization status
 exports.getlogin = function(req, res){ if (!req.session.authorized){ //   ,         global.controllers.stayonlinesessions.getsession(req.cookies.sessionid, function(error, user){ if (error){ res.send({ error: true, msg: 'You are not loggined' }); return; } req.session.authorized = true; req.session.user_id = user._id; req.session.username = user.email; res.send({ error: false, displayname: req.session.username }); }); }else{ res.send({ error: false, displayname: req.session.username }); } }; 

A few moments about cookies. If we do not specify the storage time when initializing the sessions module, they are not stored in the browser at all. It is treated by one simple setting.
 app.use(express.session({ secret: 'fegwegwe', store: new MongoStore(dbsettings), cookie: { path: '/', httpOnly: true, maxAge: 1000*60*60*24 } // maxAge -     })); 

On the client side, working with cookies is quite simple. I used the jquery.cookie.js plugin
 jQuery.cookie('sessionid',msg.sessionid); 

As a result, we received an authorization, registration, the “remember me” function, then we will have to send a letter with confirmation of the email address, a password recovery, but this is next time.

Source: https://habr.com/ru/post/154755/


All Articles