快速使用node.js進行web開發(fā)詳解
首先關(guān)于node.js的學(xué)習(xí),這里推薦一本比較好的教程,nodejs web開發(fā)指南,該書通俗易懂地將node.js語言特性講解完之后,又從一個項目角度帶領(lǐng)讀者使用node.js學(xué)習(xí)web開發(fā)。相信這是一個比較好的學(xué)習(xí)模式和過程。由于這本書是2012年出的,書中的一個web教學(xué)項目是開發(fā)一個微博。從2012到現(xiàn)在,node.js及其生態(tài)環(huán)境發(fā)生了很大改變,所以關(guān)于該書的學(xué)習(xí)如果照著書本顯然是過于陳舊的。到目前為止,node.js的web開發(fā)框架已經(jīng)升級到了Express4.12.1,對于MongoDB的操作更多是使用mongoose這個對象模型,而不是之前mongoDB 官方提供的原生node.js的API,所以本文將基于nodejsV0.1033 + MongoDBV3.0.2+ Jade1.9.2 + mogooseV4.0.1來重構(gòu)該書中的微博項目,這個組合也是目前最新的使用node.js進行web開發(fā)的常用組合之一,如果需要入門使用node.js進行web開發(fā),正在學(xué)習(xí)nodejs web開發(fā)指南的和想快速了解node.js web開發(fā)模式的朋友,相信本文是有一定幫助意義的。
1.express框架安裝
1)在node命令行模式下輸入以下命令
npm install -g express
該命令在全局環(huán)境下安裝express框架,在安裝完這一步之后,并不能直接使用express命令來生成express項目,需要再安裝一個express項目生成器,在express2.X的版本中是不需要的,express4.X版本之后將項目生成器和express本身分離了出來,如果不安裝express-generator這個生成器就使用express命令來生成項目,會遇到報express不是內(nèi)部或外部命令這個錯誤,這是需要注意的地方,nodejs web開發(fā)指南原書中是沒有安裝express-generator這一步的。
2)安裝express-generator
npm install -g express-generator
3)生成一個項目
cd .. mkdir microblog cd microblog express micorblog
這里隨意在硬盤某個目錄下創(chuàng)建一個microblog的文件夾,進入該文件夾,然后使用express microblog命令創(chuàng)建了一個microblog的express項目。
生成結(jié)構(gòu)如下:
其中app.js是項目入口文件,package.json是npm 包管理文件,bin文件夾里面的www.js放一些全局配置項以及命令行配置等。public 文件夾是用來存放項目靜態(tài)文件目錄如js,css以及圖片,routes文件夾是用來存放路由監(jiān)聽的代碼相關(guān)文件。views文件夾用來存放模板文件,這里需要注意的是express4.X使用jade作為項目的默認模板引擎,而在原書中是使用ejs作為模板引擎的,所以這里默認生成的是jade文件。無可否認ejs是要簡單些,但是原理都是一樣的,我們使用jade作為開發(fā)的模板引擎。
4)啟動項目并查看
cd microblog npm install npm start
進入到microblog文件夾,安裝項目所需相關(guān)模塊(根據(jù)pacakge.json文件),然后啟動項目,這時候打開瀏覽器查看項目輸入地址localhost:3000,結(jié)果如下說明一切正常,
到目前為止,我們已經(jīng)擁有了一個在瀏覽器中運行的web項目雛形。下面進行開發(fā),原書中的微博項目的主要功能是用戶能夠注冊登錄,權(quán)限控制并讓用戶發(fā)布微博在用戶個人主頁和項目首頁分別顯示,這些功能完整版代碼會提供,由于篇幅原因,這里以用戶注冊登錄模塊來說明如何進行一個完整流程的web開發(fā)。
2.頁面布局
依照web開發(fā)流程,我們首先來構(gòu)建一個項目主頁,項目主頁是由布局文件layout.jade和內(nèi)容文件index.jade組成,關(guān)于的jade的學(xué)習(xí),這里提供兩個地址,對于以前使用過類似模板引擎如smarty,razor等的,可以看看文檔就能夠上手做了,基本原理都是大同小異。
打開views文件,將layout.jade文件代碼改寫如下:
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') body nav.header ul.list li.logo a(href='/') Microblog li a(href='/') 首頁 li a(href='/login') 登錄 li a(href='/reg') 注冊 div.container block content hr footer.footer p a() myzhibie | @2015
需要注意父級元素和子元素的換行之間縮進,jade是利用縮進來區(qū)別代碼層級的。
首頁內(nèi)容文件index.jade
extends layout block content main.main section.intro if message h3.indexmes #{message} //如果用戶登錄或者注冊成功并且沒有在登錄狀態(tài)下點擊注冊或者登錄 if success&&user h1.welcome #{success},歡迎 #{user} 來到 Microblog else if !success&&user h1.welcome 歡迎 #{user} 來到 Microblog else h1.welcome 歡迎來到 Microblog h3.tech Microblog是一個基于Node.js,使用express4.12.1,jade1.9.2以及MongoDB搭建起來的微博系統(tǒng),是對Node.js開發(fā)指南一書中教學(xué)項目的重構(gòu)。 p.btnlist if user a.login(href='/logout') 退出 a.userlink(href='/users/#{user}') 發(fā)表文章 else a.login(href='/login') 登錄 a.register(href='/reg') 立即注冊 section.show each val in posts article.col h3.author #{val.user}說 p | #{val.post}
首頁內(nèi)容是繼承了模板文件layout.jade.原書中使用的bootstrap來構(gòu)建頁面的css布局和樣式,這里我自己手寫了一個仿bootstrap風(fēng)格的布局樣式,沒有應(yīng)用bootstrap,style.css文件如下:
body { padding: 50px; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } html,body,ul,p,hr,h3{ margin:0; padding: 0; } a { color: #00B7FF; } .header{ background:#337aB7; width: 100%; height: 60px; color: #fff; font-size: 22px; overflow: hidden; } .list{ line-height: 60px; } .navigation{ overflow: hidden; } .list li{ list-style: none; float: left; display: inline-block; margin-left: 20px; margin-right: 20px; } .list li a{ text-decoration: none; color: #fff; } .list li a:hover{ } .list li:not(:first-child) a:hover{ font-size: 26px; color: #F5F5F5; } .logo{ font-size: 26px; font-weight: 700; } .container{ min-height: 500px; text-align: center; width: 100%; } .footer{ width: 100%; height: 50px; font-size: 22px; background:#F5F5F5 ; line-height: 50px; } .footer a{ color:#337aB7; text-decoration: none; } .main{ color: #000000; width: 96%; margin: 30px auto; } .intro{ width: 100%; margin:0 auto; border-radius: 5px; height: 300px; background:#F5F5F5 ; } .userintro{ width: 100%; margin:0 auto; border-radius: 5px; height: 200px; background:#F5F5F5 ; } .welcome{ padding-top: 50px; padding-left:50px; font-size: 50px; text-align: left; padding-bottom: 0; margin: 0; } .tech{ text-align: left; padding-left:50px; margin: 0; } .show{ overflow: hidden; width: 100%; } .show li{ text-align: left; font-size: 18px; } .col{ display: inline-block; float: left; width: 32%; height: 100px; overflow: hidden; padding-right: 20px; text-align: left; text-overflow: ellipsis; } .author{ margin-top: 10px; margin-bottom: 3px; } .btnlist{ padding-left: 50px; text-align: left; } .login{ display: inline-block; padding-left: 15px; padding-right: 15px; height: 38px; line-height: 40px; background: -webkit-gradient(linear, left top, left bottom, from(#0068A6), to(#337aB7)); color: #fff; text-align: center; border-radius: 5px; font-size: 20px; font-weight: 600; border: 1px solid #ccc; text-decoration: none; margin-right: 10px; } .register{ display: inline-block; padding-left: 15px; padding-right: 15px; height: 38px; line-height: 40px; background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#F5F5F5)); color: #000; text-align: center; border-radius: 5px; font-size: 20px; font-weight: 600; border: 1px solid #ccc; text-decoration: none; } .field{ margin-top: 20px; margin-left: 50px; text-align: left; margin-bottom: 20px; border:none; border-bottom: 1px solid #ccc; } .label{ font-size: 18px; font-weight: 600; line-height: 100%; display: inline-block; width: 10%; vertical-align: middle; text-align: right; padding-right: 10px; } .regheader{ text-align: left; font-size: 24px; font-weight: 600; } .regform{ text-align: left; padding-left: 100px; margin-bottom: 20px; } .regform input[type='text'],input[type='password']{ width: 200px; height: 20px; } .regform input[type='submit']{ width: 120px; height: 30px; color: #fff; background:-webkit-gradient(linear, left top, left bottom, from(#0068A6), to(#337aB7)); border-radius: 5px; font-size: 20px; } .item{ margin:20px; width: 100%; } .mess{ font-size: 18px; color: #E73C3C; background: #F2DEDE; border-radius: 5px; width: 300px; text-align: center; margin-left: 100px; } .indexmes{ height: 30px; line-height: 30px; background: #F2DEDE; color: #E73C3C; } .article{ width: 60%; height: 30px; border-radius: 3px; border: 1px solid #A3C732; margin-top: 5px; font-size: 20px; } .submit{ height: 40px; vertical-align: middle; padding: 0; margin-top: -5px; margin-left: 5px; width: 80px; background: #A3c732; font-size: 20px; border: none; border-radius: 5px; color: #fff; } .submitform{ margin-top: 25px; margin-left: -10px; } .userlink{ display: inline-block; text-decoration: none; line-height: 38px; height: 38px; vertical-align: middle; padding: 0; margin-top: -8px; margin-left: 5px; width: 90px; text-align: center; background: #A3c732; font-size: 20px; font-weight: 600; border-radius: 5px; color: #fff; border: 1px solid #ccc; } .usertitle{ text-align: left; padding-top: 5px; padding-bottom: 0; padding-left: 5px; margin-bottom: 8px; } .usersuccess{ height: 30px; background: #DFF0D8; line-height: 30px; color: #3C7668; }
這個css文件是項目中所有的css全部包含在這里,所以比較龐大。到目前為止,可以查看首頁效果如下:
首頁中的數(shù)據(jù)都是之前自己測試過程中加入的,這里主要為了查看首頁效果,可以忽略這些數(shù)據(jù)。
由于這里要演示用戶注冊登錄模塊,用戶注冊模塊的模板文件reg.jade如下:
extends layout block content h3.field.regheader #{title} form.regform(method='post') p.mess #{message} div.item label.label(for='username') 用戶名 input(type='text',placeholder='輸入注冊用戶名',id='username',name='username') div.item label.label(for='password') 用戶密碼 input(type='password',placeholder='用戶密碼',id='password',name='password') div.item label.label(for='passwordconf') 重復(fù)密碼 input(type='password',placeholder='重復(fù)密碼',id='passwordconf',name='passwordconf') div.item label.label input(type='submit' id='sub',name='sub' value='注冊')
用戶登陸模板login.jade如下:
extends layout block content h3.field.regheader #{title} form.regform(method='post') p.mess #{message} div.item label.label(for='username') 用戶名 input(type='text',placeholder='輸入登陸用戶名',id='username',name='username') div.item label.label(for='password') 用戶密碼 input(type='password',placeholder='用戶密碼',id='password',name='password') div.item label.label input(type='submit' id='sub',name='sub' value='登陸')
最終用戶注冊效果如下:
用戶登錄模塊和這個效果相仿,就不查看了,少了一個重復(fù)密碼的input而已。
下面我們需要編寫用戶注冊的邏輯,在編寫用戶注冊邏輯的前,用戶數(shù)據(jù)需要持久化,所以首先要安裝MongoDB數(shù)據(jù)庫在自己的機器上.
MongoDB這種nosql類型的數(shù)據(jù)庫,非常適合用戶存儲JSON對象類型的數(shù)據(jù),有了mongoDB,就可以免去數(shù)據(jù)庫表設(shè)計部分的工作,對比以前使用的mysql,sqlserver以及oracle還是非常方便的。關(guān)于mongoDB數(shù)據(jù)庫的熟悉和學(xué)習(xí),推薦其官網(wǎng),官網(wǎng)詳細介紹了該數(shù)據(jù)庫的一切。英文不好可以去中文社區(qū)。同時為了使用nodejs來操作mongoDB數(shù)據(jù)庫,我們使用mongoose這個對象模型,它是將mongoDB中的一個集合映射為nodejs中的一個model,然后在該model上提供操作這個集合的一些方法,使用它就可以避免我們自己利用nodejs提供的原生操作mongoDB數(shù)據(jù)庫的語法去手寫數(shù)據(jù)庫CURD的方法,大大見曬了工作量,提高了開發(fā)效率。關(guān)于mongoose的學(xué)習(xí),推薦去其官網(wǎng),里面詳述了它的安裝,使用以及API調(diào)用情況。
解決了mongoDB安裝和操作問題,我們來對數(shù)據(jù)庫操作的model類,首先在項目路徑下建立一個db.js文件,用來連接數(shù)據(jù)庫并對數(shù)據(jù)庫進行全局配置,如下
db.js
var settings=require("./settings"); var mongoose=require('mongoose'); mongoose.connect("mongodb://"+settings.ip+"/"+settings.db); var db=mongoose.connection; module.exports={ "dbCon":db, "mongoose":mongoose };
這里首先加載了配置文件settings.js文件,為了數(shù)據(jù)庫便于靈活修改,我們將某些信息存儲在配置文件中。然后加在了之前安裝的mongoose模塊,然后調(diào)用該模塊的connect方法來連接我們配置的數(shù)據(jù)庫,然后將連接以對象的形式返回供外部調(diào)用。
settings.js
module.exports={ "ip":"localhost", "db":"microblog", "host":27071 };
MongoDB的默認端口是27071,一般可以使用默認端口即可,數(shù)據(jù)庫連接大時候可以不指定端口,數(shù)據(jù)庫名為microblog.
然后以db.js返回的數(shù)據(jù)庫連接對象為基礎(chǔ),我們在項目根目錄下創(chuàng)建一個models文件夾,用來存放數(shù)據(jù)模型。創(chuàng)建一個user.js映射我們數(shù)據(jù)庫中的user集合(可以理解為user表),代碼如下:
var mongoose=require('../db').mongoose; var schema=new mongoose.Schema({ name:'string', password:'string' }); var User=mongoose.model('User',schema); module.exports=User;
這里首先獲得db.js中定義的連接對象,并以該對象為基礎(chǔ)構(gòu)造一個Schema(架構(gòu)),mogoose操作數(shù)據(jù)庫是以架構(gòu)為基礎(chǔ)的,類似于我們其他ORM模型中屬性和方法的定義。這里我們定義了一個架構(gòu),擁有兩個屬性,name和password,都是string類型,對應(yīng)用戶的用戶名和密碼。然后利用該架構(gòu)去創(chuàng)建一個model,該model上定義了對數(shù)據(jù)集合的增刪改查等方法,不用我們自己再去定義和編寫其他代碼。在原書中這一節(jié)是利用node.js操作MongoDB數(shù)據(jù)庫的原生API去定義了一個user對象,然后在user對象上自定義了一些CRUD的方法。可以看出,直接使用Mongoose可以大大減少開發(fā)量并且擁有更好的效率和性能。
到目前為止,我們已經(jīng)有了界面(view),數(shù)據(jù)模型(model),就差邏輯代碼(controller)沒有編寫了。在編寫邏輯代碼之前需要先說下express框架的特點以及它的整體運行方式。由于本人使用過一些類似的如Asp.net mvc,Yii以及thinkphp等MVC框架,使用express之后最大的感覺是這個框架夠輕量級,尤其是express4.X之后,它僅僅保留了靜態(tài)文件路徑映射模塊作為該框架本身的內(nèi)置模塊,其他的功能都以中間件的形式采用require(modulename)進行引入,只有引入后才能夠使用該模塊提供的功能。
express的工作原理是客戶端發(fā)送一個request,express接到該請求,可以將它進行處理之后傳遞給其他中間件進行處理,最終處理完成之后,采用respond.end或者response.render進行頁面渲染或響應(yīng),進行頁面渲染的時候,采用參數(shù)傳遞頁面需要的數(shù)據(jù)給對應(yīng)模板引擎,模板引擎收到數(shù)據(jù)然后按照自己的語法進行替換生成對應(yīng)的html,最終返回給瀏覽器進行渲染。
在express中,最關(guān)鍵的部分就是路有機制,我們所有基于請求做出的響應(yīng)都是對該路由進行監(jiān)聽捕獲的結(jié)果。舉個例子,如果我們請求一個路徑為http://localhost:3000/user,那么必須在routes文件夾下面的路徑監(jiān)聽(暫且叫做監(jiān)聽吧)的js文件中編寫對該請求的響應(yīng)代碼,諸如app.post('/user',function(...){...})之類的代碼,如果不存在這樣的代碼,就會報一個404錯誤,因為請求沒有得到響應(yīng),express實例不知道怎么去響應(yīng)這個請求。以上就是express大致的原理和工作流程,對于它的學(xué)習(xí),推薦去express官網(wǎng)直接去看文檔,講的很詳細。
現(xiàn)在回到用戶注冊模塊,我們注冊用戶常見的做法是注冊成功之后就默認用戶已經(jīng)登錄,直接跳轉(zhuǎn)到歡迎登陸界面。在這里我們需要將用戶數(shù)據(jù)在注冊成功之后保存在session中,express框架對于session的支持是通過中間件express-session來的,使用方式依然是在npm 下安裝,然后在項目主文件中使用require加載,最后調(diào)用其提供的API,為了使用session,必須先安裝cookie的支持,這里利用cookie-parser這個中間件來為express框架提供cookie支持,它的具體使用方式可以去上面提供的地址自行查看。對于session,我們常見框架的做法是在服務(wù)器端將其存放到文件當中,由于這里我們有了MongoDB數(shù)據(jù)庫,更理想的狀態(tài)是將它存在數(shù)據(jù)庫中,這樣可以更靈活去控制。使用connect-mongo中間件可以將session存儲到mongoDB中,具體使用方式可按地址查看。
上述概念明確之后,我們在項目根目錄下的app.js(項目入口文件)中加載我們需要的中間件模塊和自定義的模塊如下:
app.js模塊加載代碼:
var express = require('express'); var path = require('path'); var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var routes = require('./routes/index'); var users = require('./routes/users'); var session = require("express-session"); var MongoStore=require('connect-mongo')(session); var db = require('./db'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); // uncomment after placing your favicon in /public //app.use(favicon(__dirname + '/public/favicon.ico')); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use(session({ secret:"myzhibie", store:new MongoStore({ mongooseConnection:db.dbCon }) })); app.use('/', routes); app.use('/users', users);
上述代碼就是加載各個中間件模塊并采用app.use來load這個模塊,其中上述代碼的最后一指定了將session存儲在MongoDB數(shù)據(jù)庫中,secret屬性是對session的簽名,通常是一個字符串,這是必選項,如果不寫,
是無法完成將session存儲進入數(shù)據(jù)庫的,關(guān)于該功能的更詳細介紹請查看文檔,最后兩句app.use('/',routes)和app.use('/users',users)代表對于這兩個路由的訪問處理代碼我們封裝在了routes和users模塊中,er這兩個模塊都在routes文件夾下面。
完成了模塊引入加載和一些基本的設(shè)置,現(xiàn)在來編寫用戶注冊的邏輯代碼,上面說到對于路徑/的訪問處理在routes模塊中,這個模塊指的就是routes文件夾下面的index.js,部分代碼如下:
var express = require('express'); var crypto = require('crypto'); var router = express.Router(); var db=require('../db'); var User=require('../models/user'); var Post=require('../models/post'); /* GET home page. */ router.get('/', function(req, res, next) { Post.find({},function(err,posts){ if(err){ req.session.message=err.message; return res.redirect('/'); } res.render('index',{ posts:posts }); }); }); //發(fā)表微博 router.post('/post',function(req, res, next){ var currentUser=req.session.user; var post=new Post({ user:currentUser.name, post:req.body.article, updated:getTime(new Date()) }); post.save(function(err){ if(err){ req.session.message=err.message; return res.redirect('/reg'); } req.session.success="發(fā)表成功"; res.redirect('/users/'+currentUser.name); }); }); function getTime(date){ return date.getFullYear()+ "-"+date.getMonth()+1+"-"+ date.getDate()+" "+ date.getHours()+":"+ date.getMinutes(); } router.get('/reg', isLogin); //用戶進入注冊頁面 router.get('/reg',function(req,res){ res.render('reg',{title:"用戶注冊"}); }); router.post('/reg', isLogin); //用戶點擊注冊按鈕 router.post('/reg',function(req,res){ if(req.body['password']!= req.body['passwordconf']){ req.session.error="兩次密碼不一致"; return res.redirect('/reg'); } var md5=crypto.createHash('md5'); var password=md5.update(req.body.password).digest('base64'); var newUser=new User({ name:req.body['username'], password:password }); User.findOne({name:newUser.name},function(err,user){ if(user){ err="用戶名已經(jīng)存在"; } if(err){ req.session.error=err; return res.redirect('/reg'); } newUser.save(function(err){ if(err){ req.session.error=err.message; return res.redirect('/reg'); } req.session.user=newUser; req.session.success="注冊成功"; res.redirect('/'); }); }); }); router.get('/login',isLogin); router.get('/login',function(req,res){ res.render('login',{title:"用戶登陸"}); }); router.post('/login',isLogin); router.post('/login',function(req,res){ var md5=crypto.createHash('md5'); var password=md5.update(req.body.password).digest('base64'); User.findOne({name:req.body.username},function(err,user){ if(!user){ req.session.error="用戶不存在"; return res.redirect('/login'); } if(user.password!=password){ req.session.error="密碼錯誤"; return res.redirect('/login'); } req.session.user=user; req.session.success="登錄成功"; res.redirect('/'); }); }); router.get('/logout',function(req,res){ req.session.user=null; res.redirect('/'); }); function isLogin(req,res,next){ if(req.session.user){ req.session.message="用戶已登錄"; return res.redirect('/'); } next(); } module.exports = router;
上述代碼1-6行都是對外部模塊的引入,8-19行是對首頁路由/的處理代碼。117行將該模塊定義為router供外部調(diào)用。我們主要看54-83行,這些代碼就是用戶注冊的代碼,54行監(jiān)聽來自用戶對于/reg路由的post請求,首先判斷兩次密碼是否一致,如果不一致在session中存儲一個錯誤信息然后跳轉(zhuǎn)到到當前頁面顯示錯誤信息,該錯誤信息供模板引擎顯示給用戶。如果兩次密碼一致首先對密碼進行md5加密,使用的是nodejs提供的核心模塊crypto,并生成一個對象模型User,該對象模型是mongoose中提供的一個model的實例,mongoose在它上面定義了一些操作數(shù)據(jù)庫的方法。然后調(diào)用這個實例的findOne方法檢測該用戶是否已經(jīng)存在,如果存在就保存錯誤信息到session并跳轉(zhuǎn)到當前頁顯示錯誤。如果不存在這樣一個用戶就使用save方法進行用戶信息保存,注冊成功后將用戶信息保存在session中,并保存一個success的提示信息,然后跳轉(zhuǎn)到首頁。這里需要注意一個坑,以前做php或者.net的時候,我們通常都是先查詢數(shù)據(jù)庫等數(shù)據(jù)庫返回結(jié)果提示用戶是否存在之后再進行用戶的save然后在跳轉(zhuǎn),這是一種同步方式,跳轉(zhuǎn)操作需要等待findOne操作返回結(jié)果之后才能進行。而nodejs中采用異步IO,最后的跳轉(zhuǎn)操作需要放在findOne操作的回調(diào)函數(shù)中進行,跳轉(zhuǎn)操作不必等待findone操作結(jié)束后執(zhí)行,兩者是異步的。如果將最后的redirect操作放在findOne操作外部而不是回調(diào)函數(shù)中,你會在控制臺上得到一個Can't set headers after they are sent的錯誤,這是因為在fineOne以及save操作之前已經(jīng)進行行了跳轉(zhuǎn),response響應(yīng)已經(jīng)結(jié)束,不能夠重復(fù)響應(yīng)請求。
到目前為止,用戶注冊模塊基本上已經(jīng)差不多完成了,最后需要說一下如何在頁面上顯示提示信息或者錯誤信息,之前我們將提示信息或者錯誤信息都保存在了session中,jade要顯示錯誤信息,它是不能夠直接訪問session的,在express2.X即原書中是利用req.flash API+動態(tài)視圖助手來實現(xiàn)的,就是發(fā)生錯誤的時候先將其利用req.flash方法存儲下來,然后利用動態(tài)視圖助手結(jié)合模板去渲染給用戶。express4.X廢棄了這種方式,我們可以利用req.flash 的原理來自己模擬一個這種機制,同時利用res.locals變量被保存起來,模板在渲染的時候是能夠訪問到服務(wù)端這個變量的。關(guān)于res.locals的更多介紹請查看文檔。
為了模擬這種req.flash機制,我們在項目入口文件app.js(項目根目錄下)添加一段代碼如下:
app.use(function(req,res,next){ // res.locals.user=req.session.user; var err=req.session.error; var success=req.session.success; var user=req.session.user; var mess=req.session.message; delete req.session.success; delete req.session.error; delete req.session.message; if(err){ res.locals.message="*"+err; } if(mess){ res.locals.message="*"+mess; } if(success){ res.locals.success=success; } if(user){ res.locals.user=user.name; } next(); });
這段代碼的意思是用戶請求和響應(yīng)的時候,捕獲session中存儲的錯誤信息和用戶提示,將其存儲在response.locals變量中,這樣模板就能夠獲取。對于錯誤信息和提示,由于只使用一次,存儲后立即使用delete刪除,對于用戶信息,需要持久保存下來,則不刪除。
這樣,就能夠顯示用戶提示或者錯誤信息。
下面演示一下完整的用戶注冊流程以及錯誤信息提示。
當用戶名存在或密碼不一致時,
當注冊成功后跳轉(zhuǎn)到首頁并顯示用戶注冊成功
同時對于注冊成功和登陸成功擁有不同提示,如果該用戶已經(jīng)是登錄狀態(tài)則顯示退出和發(fā)表文章按鈕,如果沒有登錄,則顯示的是登陸和立即注冊按鈕。
以上就是利用nodejs及express,mongoose,mongoDB,jade進行web開發(fā)的主要流程,由于該項目是對nodejs web開發(fā)指南一書中微博項目的重構(gòu),所以完整版的項目代碼還有用戶權(quán)限控制(已登錄用戶不能夠注冊或登陸并提示),用戶進入個人頁面發(fā)布微博并列表顯示,同時首頁顯示最近發(fā)布的微博信息等功能。完整版代碼點這里,由于篇幅以及時間問題,上述要點不可能一一展開討論,本文就作為一個提綱,是對nodejs web開發(fā)的一個綜述。
相關(guān)文章
深入理解Node.js 事件循環(huán)和回調(diào)函數(shù)
這篇文章主要介紹了深入理解Node.js 事件循環(huán)和回調(diào)函數(shù),詳細的介紹Node.js 事件循環(huán)和Node.js回調(diào)函數(shù),需要學(xué)習(xí)的可以參考一下。2016-11-11參考?EventEmitter實現(xiàn)一個簡單的訂閱發(fā)布功能函數(shù)
這篇文章主要為大家介紹了參考?EventEmitter實現(xiàn)一個簡單的訂閱發(fā)布功能函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02學(xué)習(xí)使用grunt來打包JavaScript和CSS程序的教程
這篇文章主要介紹了學(xué)習(xí)使用grunt來打包JavaScript和CSS程序的教程,grunt基于node.js和需要的朋友可以參考下2016-01-01NodeJS模塊與ES6模塊系統(tǒng)語法及注意點詳解
這篇文章主要給大家介紹了關(guān)于NodeJS模塊與ES6模塊系統(tǒng)語法及注意點的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01我的Node.js學(xué)習(xí)之路(四)--單元測試
在專業(yè)化的軟件開發(fā)過程中,無論什么平臺語言,現(xiàn)在都需要UnitTest單元測試. Node.js有built-in的Assert。 今天讓我們來看一下Node.js的單元測試。在這兒我們使用nodeunit2014-07-07node.JS的crypto加密模塊使用方法詳解(MD5,AES,Hmac,Diffie-Hellman加密)
本文將詳細介紹node.JS的加密模塊crypto實現(xiàn)MD5,AES,Hmac,Diffie-Hellman加密的詳解方法,需要的朋友可以參考下2020-02-02node.js?express和koa中間件機制和錯誤處理機制
這篇文章主要介紹了node.js?express和koa中間件機制和錯誤處理機制,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-07-07