這個小應用使用到了node.js bootstrap express 以及數據庫的操作 :使用mongoose對象模型來操作 mongodb
如果沒了解過的可以先去基本了解一下相關概念~
首先注明一下版本,因為express因為版本的不同使用的方式也不同,我這算是目前最新的了吧

還沒有裝express的可以移步到 這里 看看express框架的獲取安裝
1.簡單地項目初始化
進入你的nodejs安裝路徑下邊,如圖,然后執行命令 express -e test (這里把項目名設置為test)

出現如上圖所示,看到install dependencies沒有,它說如果你想安裝依賴就先進入項目test目錄,然后執行 npm install安裝依賴模塊。
那就開始吧,網絡環境差的可能安裝會出錯..出現很長一大串一般就行了

如此一來,項目初始已經完成,可以運行一下項目 npm start 看是否正常。


ok 還算正常,下面先來基本分析一下生成的初始項目:

之前 那篇文章 已經說過
項目創建成功之后,生成四個文件夾,主文件app.js與配置信息文件packetage.json
bin是項目的啟動文件,配置以什么方式啟動項目,默認 npm start
public是項目的靜態文件,放置js css img等文件
routes是項目的路由信息文件,控制地址路由
views是視圖文件,放置模板文件ejs或jade等(其實就相當於html形式文件啦~)
express這樣的MVC框架模式,是一個Web項目的基本構成。
先來看看文件信息package.json 一般項目的主要信息都會在這里產生
{ "name": "test", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "body-parser": "~1.12.0", "cookie-parser": "~1.3.4", "debug": "~2.1.1", "ejs": "~2.3.1", "express": "~4.12.2", "morgan": "~1.5.1", "serve-favicon": "~2.2.0" } }
看看主文件 app.js 這是它的初始形式,這個模塊還要繼續導出給 bin文件夾下的www文件使用
1 var express = require('express'); 2 var path = require('path'); 3 var favicon = require('serve-favicon'); 4 var logger = require('morgan'); 5 var cookieParser = require('cookie-parser'); 6 var bodyParser = require('body-parser'); 7 8 var routes = require('./routes/index'); 9 var users = require('./routes/users'); 10 11 var app = express(); 12 13 // view engine setup 14 app.set('views', path.join(__dirname, 'views')); 15 app.set('view engine', 'ejs'); 16 17 // uncomment after placing your favicon in /public 18 //app.use(favicon(__dirname + '/public/favicon.ico')); 19 app.use(logger('dev')); 20 app.use(bodyParser.json()); 21 app.use(bodyParser.urlencoded({ extended: false })); 22 app.use(cookieParser()); 23 app.use(express.static(path.join(__dirname, 'public'))); 24 25 app.use('/', routes); 26 app.use('/users', users); 27 28 // catch 404 and forward to error handler 29 app.use(function(req, res, next) { 30 var err = new Error('Not Found'); 31 err.status = 404; 32 next(err); 33 }); 34 35 // error handlers 36 37 // development error handler 38 // will print stacktrace 39 if (app.get('env') === 'development') { 40 app.use(function(err, req, res, next) { 41 res.status(err.status || 500); 42 res.render('error', { 43 message: err.message, 44 error: err 45 }); 46 }); 47 } 48 49 // production error handler 50 // no stacktraces leaked to user 51 app.use(function(err, req, res, next) { 52 res.status(err.status || 500); 53 res.render('error', { 54 message: err.message, 55 error: {} 56 }); 57 }); 58 59 60 module.exports = app;
www文件內容:這里擁有着http服務器的基本配置
1 #!/usr/bin/env node 2 3 /** 4 * Module dependencies. 5 */ 6 7 var app = require('../app'); 8 var debug = require('debug')('test:server'); 9 var http = require('http'); 10 11 /** 12 * Get port from environment and store in Express. 13 */ 14 15 var port = normalizePort(process.env.PORT || '3000'); 16 app.set('port', port); 17 18 /** 19 * Create HTTP server. 20 */ 21 22 var server = http.createServer(app); 23 24 /** 25 * Listen on provided port, on all network interfaces. 26 */ 27 28 server.listen(port); 29 server.on('error', onError); 30 server.on('listening', onListening); 31 32 /** 33 * Normalize a port into a number, string, or false. 34 */ 35 36 function normalizePort(val) { 37 var port = parseInt(val, 10); 38 39 if (isNaN(port)) { 40 // named pipe 41 return val; 42 } 43 44 if (port >= 0) { 45 // port number 46 return port; 47 } 48 49 return false; 50 } 51 52 /** 53 * Event listener for HTTP server "error" event. 54 */ 55 56 function onError(error) { 57 if (error.syscall !== 'listen') { 58 throw error; 59 } 60 61 var bind = typeof port === 'string' 62 ? 'Pipe ' + port 63 : 'Port ' + port; 64 65 // handle specific listen errors with friendly messages 66 switch (error.code) { 67 case 'EACCES': 68 console.error(bind + ' requires elevated privileges'); 69 process.exit(1); 70 break; 71 case 'EADDRINUSE': 72 console.error(bind + ' is already in use'); 73 process.exit(1); 74 break; 75 default: 76 throw error; 77 } 78 } 79 80 /** 81 * Event listener for HTTP server "listening" event. 82 */ 83 84 function onListening() { 85 var addr = server.address(); 86 var bind = typeof addr === 'string' 87 ? 'pipe ' + addr 88 : 'port ' + addr.port; 89 debug('Listening on ' + bind); 90 }
再來介紹一下項目使用到的ejs模板,比如看看這個view里邊的index.ejs (我們待會可以直接把它轉為html,差不多的)
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1><%= title %></h1> <p>Welcome to <%= title %></p> </body> </html>
<%= title %> 這就是ejs的使用范例,title的值通過路由routes文件夾下index.js代碼傳入(后面再談)

好了,基本介紹了項目的初始情況
2.基於初始項目的改進-- 注冊登錄功能
設計如下:
一個初始界面(其實就是原始地址:比如 localhost:3000(index.html 路徑為/ ) ,在初始界面選擇登錄或注冊
跳進來之后會先跳進登錄界面(login.html 路徑為 /login),可以選擇先注冊(跳轉 register.html 路徑為/register)
跳進注冊界面后就會跳進(register.html 路徑為 /register),注冊成功后就跳轉登錄界面(login.html 路徑為 /login)
在登錄界面登錄成功后就跳轉(home.html 路徑為 /home). 在home這里還提供了注銷的功能(無頁面文件,它的路徑為 /logout
如果瀏覽器直接輸入localhost:3000/home 要先判斷是否登錄成功,未登錄不允許進入
看到上訴,應該了解到:我們是通過一個路徑,然后通過這個路徑的解析,從而渲染出這個路徑對應的模板文件,其中我們這里的模板文件為.html后綴的
首先展示一下基本界面形態:

然后先注冊吧,點擊注冊

填入用戶名密碼,這里稍微設置了兩次密碼相同的判斷,注冊成功它會自動跳轉登錄界面

用mongoVUE看看數據的創建

那就登錄吧,登錄成功跳轉home界面

注銷吧,注銷后清除session值,然后跳轉到根路徑

然后試一下瀏覽器直接進入 home路徑? 瀏覽器地址輸入 localhost:3000/home 回車, ok 它自動跳轉到登錄界面

好現在開始解析如何構建這個小項目:
因為我們直接使用了后綴名 .html ,所以我們要先修改一下ejs模板 ,再把原來views目錄下模板文件后綴改成 .html
var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); app.engine("html",require("ejs").__express); // or app.engine("html",require("ejs").renderFile); //app.set("view engine","ejs"); app.set('view engine', 'html');
其實就是加一句再改一句。 __express 和renderFile都可以, 不用管它是什么,它能那樣用就行了
然后我們知道需要這些模板文件,那就創建它們吧

index.html 其中 <%= title %>使用到了模板 連接<a> 直接使用了路由路徑的方法
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> <style type="text/css"> a{margin-left: 20px; text-decoration: none;} a:hover{text-decoration: underline;} </style> </head> <body> <h1><%= title %></h1> <p>Welcome to <%= title %></p> <p><a href="/login">登錄 </a> <a href="/register"> 注冊</a> </p> </body> </html>
register.html 注冊方式主要是把原始 form表單 onsubmit="return false" 防止默認提交,然后在輸入信息正確的情況下,通過ajax,把表單信息post到路徑/register
然后我們就通過路由功能根據此路徑來處理信息(這個跟ajax和php交互是同一個道理)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title><%= title %></title> <link rel="stylesheet" href="stylesheets/bootstrap.min.css" media="screen"> <style type="text/css"> .m15{ margin: 15px;} .tc{ text-align: center;font-size: 18px;font-weight: 600;} </style> </head> <body screen_capture_injected="true"> <div class="container"> <%- message %> <form class="col-sm-offset-4 col-sm-4 form-horizontal" role="form" method="post" onsubmit="return false"> <fieldset> <legend></legend> <div class="panel panel-default"> <div class="panel-heading"> <p class="tc">注冊信息</p> </div> <div class="panel-body m15"> <div class="form-group"> <div class="input-group"> <span class="input-group-addon"> <span class="glyphicon glyphicon-user"></span> </span> <input type="text" class="form-control" id="username" name="username" placeholder="請輸入用戶名" required> </div> </div> <div class="form-group"> <div class="input-group"> <span class="input-group-addon"> <span class="glyphicon glyphicon-lock"></span> </span> <input type="text" class="form-control" id="password" name="password" placeholder="請輸入密碼" required> </div> </div> <div class="form-group"> <div class="input-group"> <span class="input-group-addon"> <span class="glyphicon glyphicon-lock"></span> </span> <input type="text" class="form-control" id="password1" name="password1" placeholder="請再次輸入密碼" required> </div> </div> <div class="form-group"> <button type="submit" class="btn btn-primary btn-block" id="register1">注冊</button> </div> <div class="form-group"> <button type="button" class="btn btn-info col-sm-2 col-sm-offset-10" id="login1">登錄</button> </div> </div> </div> </fieldset> </form> </div> <script type="text/javascript" src="javascripts/jquery.min.js"></script> <script type="text/javascript" src="javascripts/bootstrap.min.js"></script> <script type="text/javascript"> $(function(){ $("#login1").click(function(){ location.href = 'login'; }); $("#register1").click(function(){ var username = $("#username").val(); var password = $("#password").val(); var password1 = $("#password1").val(); if(password !== password1){ $("#password").css("border","1px solid red"); $("#password1").css("border","1px solid red"); }else if(password === password1){ var data = {"uname":username,"upwd":password}; $.ajax({ url: '/register', type: 'post', data: data, success: function(data,status){ if(status == 'success'){ location.href = 'login'; } }, error: function(data,err){ location.href = 'register'; } }); } }); }); </script> </body> </head> </html>
login.html 跟上面register.html原理差不多
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title><%= title %></title> <link rel="stylesheet" href="stylesheets/bootstrap.min.css" media="screen"> <style type="text/css"> .m15{ margin: 15px;} .tc{ text-align: center;font-size: 18px;font-weight: 600;} </style> </head> <body screen_capture_injected="true"> <div class="container"> <%- message %> <form class="col-sm-offset-4 col-sm-4 form-horizontal" role="form" method="post" onsubmit="return false"> <fieldset> <legend></legend> <div class="panel panel-default"> <div class="panel-heading"> <p class="tc">請先登錄</p> </div> <div class="panel-body m15"> <div class="form-group"> <div class="input-group"> <span class="input-group-addon"> <span class="glyphicon glyphicon-user"></span> </span> <input type="text" class="form-control" id="username" name="username" placeholder="請輸入用戶名" required> </div> </div> <div class="form-group"> <div class="input-group"> <span class="input-group-addon"> <span class="glyphicon glyphicon-lock"></span> </span> <input type="text" class="form-control" id="password" name="password" placeholder="請輸入密碼" required> </div> </div> <div class="form-group"> <button type="submit" class="btn btn-primary btn-block" id="login0">登錄</button> </div> <div class="form-group"> <button type="button" class="btn btn-info col-sm-2 col-sm-offset-10" id="register0">注冊</button> </div> </div> </div> </fieldset> </form> </div> <script type="text/javascript" src="javascripts/jquery.min.js"></script> <script type="text/javascript" src="javascripts/bootstrap.min.js"></script> <script type="text/javascript"> $(function(){ $("#register0").click(function(){ location.href = 'register'; }); $("#login0").click(function(){ var username = $("#username").val(); var password = $("#password").val(); var data = {"uname":username,"upwd":password}; $.ajax({ url:'/login', type:'post', data: data, success: function(data,status){ if(status == 'success'){ location.href = 'home'; } }, error: function(data,status){ if(status == 'error'){ location.href = 'login'; } } }); }); }); </script> </body> </head> </html>
最后是 home.html 里頭的 user.name 就是使用ejs模板通過session.user來獲取user對象,這里user有name和password的屬性
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> <style type="text/css"> a{margin-left: 20px; text-decoration: none;} a:hover{text-decoration: underline;} </style> </head> <body> <h1>Your name: <%- user.name %></h1> <p>Welcome to your home ~</p> <p><a href="/logout">我要注銷 </a> </p> </body> </html>
模板文件就是這些,接下來給主文件 app.js增加路由配置,讓瀏覽器訪問到路徑后得以被解析
app.use('/', routes); // 即為為路徑 / 設置路由
app.use('/users', users); // 即為為路徑 /users 設置路由
app.use('/login',routes); // 即為為路徑 /login 設置路由
app.use('/register',routes); // 即為為路徑 /register 設置路由
app.use('/home',routes); // 即為為路徑 /home 設置路由
app.use("/logout",routes); // 即為為路徑 /logout 設置路由
app.use是一個中間件的用法,這里的routes看初始項目的那句代碼,就是引用了routes文件夾下的index.js模塊
var routes = require('./routes/index'); var users = require('./routes/users');
所以待會我們還得繼續修改完善index.js(我這里是直接把所有路徑的處理方法全部放到index.js中,實際做的時候可以考慮細分出模塊)
這里先不說index.js,因為還有很多更寬泛的工作沒弄
1.注冊登錄,所以我們得需要數據庫
這里使用到了mongodb . 據我所知mongodb主要有兩種使用方法,這里使用了其中的一種:使用 mongoose
Mongoose是MongoDB的一個對象模型工具,是基於node-mongodb-native開發的MongoDB nodejs驅動,可以在異步的環境下執行。
同時它也是針對MongoDB操作的一個對象模型庫,封裝了MongoDB對文檔的的一些增刪改查等常用方法,讓NodeJS操作Mongodb數據庫變得更加靈活簡單。
我們通過Mongoose去創建一個“集合”並對其進行增刪改查,就要用到它的三個屬性:Schema(數據屬性模型)、Model、Entity
這里簡單介紹一下,更詳細的用法可以自行查閱~
Schema —— 一種以文件形式存儲的數據庫模型骨架,無法直接通往數據庫端,也就是說它不具備對數據庫的操作能力,僅僅只是數據庫模型在程序片段中的一種表現,可以說是數據屬性模型(傳統意義的表結構),又或着是“集合”的模型骨架。
比如定義一個Schema:
var mongoose = require("mongoose"); var TestSchema = new mongoose.Schema({ name : { type:String },//屬性name,類型為String age : { type:Number, default:0 },//屬性age,類型為Number,默認為0 time : { type:Date, default:Date.now }, email: { type:String,default:''} });
Model —— 由Schema構造生成的模型,除了Schema定義的數據庫骨架以外,還具有數據庫操作的行為,類似於管理數據庫屬性、行為的類。
比如定義一個Model:
var db = mongoose.connect("mongodb://127.0.0.1:27017/test"); // 創建Model var TestModel = db.model("test1", TestSchema);
Entity —— 由Model創建的實體,使用save方法保存數據,Model和Entity都有能影響數據庫的操作,但Model比Entity更具操作性。
比如定義一個Entity:
var TestEntity = new TestModel({ name : "Lenka", age : 36, email: "lenka@qq.com" }); console.log(TestEntity.name); // Lenka console.log(TestEntity.age); // 36
基本就介紹到這里
因為我們要使用數據庫,那就來創建它。使用的就是上述的方法
首先,在項目根目錄下建立一個database文件夾,建立文件 models.js 然后建立model處理文件 dbHandel.js

寫入文件 models.js 一個user集合,里面有name和password屬性
module.exports = { user:{ name:{type:String,required:true}, password:{type:String,required:true} } };
寫入文件 dbHandel.js 里邊主要是獲取 Schema 然后處理獲取 model ,最后就是返回一個model了(提供其他文件對model的操作 -- Entity是使用)
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var models = require("./models"); for(var m in models){ mongoose.model(m,new Schema(models[m])); } module.exports = { getModel: function(type){ return _getModel(type); } }; var _getModel = function(type){ return mongoose.model(type); };
建立好基本文件后我們就在app.js中調用使用它:要使用multer和mongoose模塊
項目沒有,所以我們要安裝


app.js中加上
var multer = require('multer'); var mongoose = require('mongoose'); global.dbHandel = require('./database/dbHandel'); global.db = mongoose.connect("mongodb://localhost:27017/nodedb"); // 下邊這里也加上 use(multer()) app.use(bodyParser.urlencoded({ extended: true })); app.use(multer()); app.use(cookieParser());
2.因為我們使用到了session(比如進入home的時候判斷session值是否為空),所以需要express-session 模塊

然后在app.js中引用它並作初始設置:
var session = require('express-session'); var app = express(); app.use(session({ secret: 'secret', cookie:{ maxAge: 1000*60*30; } })); app.use(function(req,res,next){ res.locals.user = req.session.user; // 從session 獲取 user對象 var err = req.session.error; //獲取錯誤信息 delete req.session.error; res.locals.message = ""; // 展示的信息 message if(err){ res.locals.message = '<div class="alert alert-danger" style="margin-bottom:20px;color:red;">'+err+'</div>'; } next(); //中間件傳遞 });
好現在想想我們還剩下什么:
數據庫已經提供出model接口給我們使用(給它填數據)
已經初始化了路徑處理
初始化了session信息 數據庫配置等
頁面模板也已經做完
所以剩下的就是路徑處理的部分:去routes目錄下 修改index.js吧
/ 路徑
/* GET index page. */ router.get('/', function(req, res,next) { res.render('index', { title: 'Express' }); // 到達此路徑則渲染index文件,並傳出title值供 index.html使用 });
/login 路徑
/* GET login page. */ router.route("/login").get(function(req,res){ // 到達此路徑則渲染login文件,並傳出title值供 login.html使用 res.render("login",{title:'User Login'}); }).post(function(req,res){ // 從此路徑檢測到post方式則進行post數據的處理操作 //get User info //這里的User就是從model中獲取user對象,通過global.dbHandel全局方法(這個方法在app.js中已經實現) var User = global.dbHandel.getModel('user'); var uname = req.body.uname; //獲取post上來的 data數據中 uname的值 User.findOne({name:uname},function(err,doc){ //通過此model以用戶名的條件 查詢數據庫中的匹配信息 if(err){ //錯誤就返回給原post處(login.html) 狀態碼為500的錯誤 res.send(500); console.log(err); }else if(!doc){ //查詢不到用戶名匹配信息,則用戶名不存在 req.session.error = '用戶名不存在'; res.send(404); // 狀態碼返回404 // res.redirect("/login"); }else{ if(req.body.upwd != doc.password){ //查詢到匹配用戶名的信息,但相應的password屬性不匹配 req.session.error = "密碼錯誤"; res.send(404); // res.redirect("/login"); }else{ //信息匹配成功,則將此對象(匹配到的user) 賦給session.user 並返回成功 req.session.user = doc; res.send(200); // res.redirect("/home"); } } }); });
/register 路徑
/* GET register page. */ router.route("/register").get(function(req,res){ // 到達此路徑則渲染register文件,並傳出title值供 register.html使用 res.render("register",{title:'User register'}); }).post(function(req,res){ //這里的User就是從model中獲取user對象,通過global.dbHandel全局方法(這個方法在app.js中已經實現) var User = global.dbHandel.getModel('user'); var uname = req.body.uname; var upwd = req.body.upwd; User.findOne({name: uname},function(err,doc){ // 同理 /login 路徑的處理方式 if(err){ res.send(500); req.session.error = '網絡異常錯誤!'; console.log(err); }else if(doc){ req.session.error = '用戶名已存在!'; res.send(500); }else{ User.create({ // 創建一組user對象置入model name: uname, password: upwd },function(err,doc){ if (err) { res.send(500); console.log(err); } else { req.session.error = '用戶名創建成功!'; res.send(200); } }); } }); });
/home 路徑
/* GET home page. */ router.get("/home",function(req,res){ if(!req.session.user){ //到達/home路徑首先判斷是否已經登錄 req.session.error = "請先登錄" res.redirect("/login"); //未登錄則重定向到 /login 路徑 } res.render("home",{title:'Home'}); //已登錄則渲染home頁面 });
/logout 路徑
/* GET logout page. */ router.get("/logout",function(req,res){ // 到達 /logout 路徑則登出, session中user,error對象置空,並重定向到根路徑 req.session.user = null; req.session.error = null; res.redirect("/"); });
當然了,把所以路徑的處理放在同一個index.js事實上有點糟糕,可以考慮分着寫:(這里提供一種思路分出模塊)
比如一個home.js模塊里邊:
module.exports = function ( app ) { app.get('/logout', function(req, res){ req.session.user = null; req.session.error = null; res.redirect('/'); }); }
從而只需要在index.js模塊里邊引用即可
module.exports = function ( app ) { require('./logout')(app); };
在app.js模塊中再引用一下就可以(routes目錄下index.js是默認文件,所以可以省略index)
require('./routes')(app);
3.好了,一個簡單的注冊登錄功能已經完成了,啟動項目吧
(注意:因為要使用到mongodb數據庫,所以要先開啟數據庫服務,不然無法訪問,因為我們使用了nodedb 這個數據庫,所以最后也要先在mongodb中創建它,不然也有可能出錯 未安裝數據庫的可以看看 這篇 ,檢測數據庫服務是否開啟:瀏覽器打開localhost:27017 就能訪問 ,然后給數據庫添加nodedb吧)
服務開啟

初始化nonedb可以類似這樣

啟動項目,npm start

上面那個bson錯誤的不用管它..我也不知咋處理,聽說可以直接 npm install bson 或者 npm update 就行
但我試了貌似沒什么效果

好了,項目已經打開,瀏覽器輸入 localhost:3000 訪問吧 (期間可以自己查看mongodb數據庫里邊nodedb --> user 數據的改動,使用mongoVUE或者命令查看)
需要代碼的可移步至Github: https://github.com/imwtr/nodejs_express_login_register
------------------------------ 原創作品,轉載還請注明 -------------------------------
