快速使用node.js進行web開發

首先關於node.js的學習,這裏推薦一本比較好的教程,nodejs web開發指南,該書通俗易懂地將node.js語言特性講解完以後,又從一個項目角度帶領讀者使用node.js學習web開發。相信這是一個比較好的學習模式和過程。因爲這本書是2012年出的,書中的一個web教學項目是開發一個微博。從2012到如今,node.js及其生態環境發生了很大改變,因此關於該書的學習若是照着書本顯然是過於陳舊的。到目前爲止,node.js的web開發框架已經升級到了Express4.12.1,對於MongoDB的操做更可能是使用mongoose這個對象模型,而不是以前mongoDB 官方提供的原生node.js的API,因此本文將基於nodejsV0.1033 + MongoDBV3.0.2+ Jade1.9.2 + mogooseV4.0.1來重構該書中的微博項目,這個組合也是目前最新的使用node.js進行web開發的經常使用組合之一,若是須要入門使用node.js進行web開發,正在學習nodejs web開發指南的和想快速瞭解node.js web開發模式的朋友,相信本文是有必定幫助意義的。php

1.express框架安裝css

1)在node命令行模式下輸入如下命令html

npm install -g express

該命令在全局環境下安裝express框架,在安裝完這一步以後,並不能直接使用express命令來生成express項目,須要再安裝一個express項目生成器,在express2.X的版本中是不須要的,express4.X版本以後將項目生成器和express自己分離了出來,若是不安裝express-generator這個生成器就使用express命令來生成項目,會遇到報express不是內部或外部命令這個錯誤,這是須要注意的地方,nodejs web開發指南原書中是沒有安裝express-generator這一步的。node

2)安裝express-generatormysql

npm install -g express-generator

3)生成一個項目git

cd ..
mkdir microblog
cd microblog
express micorblog

這裏隨意在硬盤某個目錄下建立一個microblog的文件夾,進入該文件夾,而後使用express microblog命令建立了一個microblog的express項目。github

生成結構以下:web

其中app.js是項目入口文件,package.json是npm 包管理文件,bin文件夾裏面的www.js放一些全局配置項以及命令行配置等。public 文件夾是用來存放項目靜態文件目錄如js,css以及圖片,routes文件夾是用來存放路由監聽的代碼相關文件。views文件夾用來存放模板文件,這裏須要注意的是express4.X使用jade做爲項目的默認模板引擎,而在原書中是使用ejs做爲模板引擎的,因此這裏默認生成的是jade文件。無能否認ejs是要簡單些,可是原理都是同樣的,咱們使用jade做爲開發的模板引擎。sql

4)啓動項目並查看mongodb

cd microblog
npm install
npm start

進入到microblog文件夾,安裝項目所需相關模塊(根據pacakge.json文件),而後啓動項目,這時候打開瀏覽器查看項目輸入地址localhost:3000,結果以下說明一切正常,

到目前爲止,咱們已經擁有了一個在瀏覽器中運行的web項目雛形。下面進行開發,原書中的微博項目的主要功能是用戶可以註冊登陸,權限控制並讓用戶發佈微博在用戶我的主頁和項目首頁分別顯示,這些功能完整版代碼會提供,因爲篇幅緣由,這裏以用戶註冊登陸模塊來講明如何進行一個完整流程的web開發。

2.頁面佈局

依照web開發流程,咱們首先來構建一個項目主頁,項目主頁是由佈局文件layout.jade和內容文件index.jade組成,關於的jade的學習,這裏提供兩個地址,對於之前使用過相似模板引擎如smarty,razor等的,能夠看看文檔就可以上手作了,基本原理都是大同小異。jade官網文檔慕課網jade教程,看看這兩個教程就基本能夠掌握了,關於jade的學習,我認爲主要是現學現用,對着文檔編寫頁面便可,固然若是有不少時間也可深刻學習。

打開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(href='http://myzhibie.coding.io') myzhibie
                   | @2015

須要注意父級元素和子元素的換行之間縮進,jade是利用縮進來區別代碼層級的。

首頁內容文件index.jade

extends layout
block content
    main.main
        section.intro
            if message
                h3.indexmes #{message}
            //若是用戶登陸或者註冊成功而且沒有在登陸狀態下點擊註冊或者登陸
            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搭建起來的微博系統,是對Node.js開發指南一書中教學項目的重構。
            p.btnlist
                if user
                    a.login(href='/logout') 退出
                    a.userlink(href='/users/#{user}') 發表文章
                else
                    a.login(href='/login') 登陸
                    a.register(href='/reg') 當即註冊
        section.show
            each val in posts
                article.col
                    h3.author #{val.user}說
                    p
                        | #{val.post}

首頁內容是繼承了模板文件layout.jade.原書中使用的bootstrap來構建頁面的css佈局和樣式,這裏我本身手寫了一個仿bootstrap風格的佈局樣式,沒有應用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所有包含在這裏,因此比較龐大。到目前爲止,能夠查看首頁效果以下:

首頁中的數據都是以前本身測試過程當中加入的,這裏主要爲了查看首頁效果,能夠忽略這些數據。

因爲這裏要演示用戶註冊登陸模塊,用戶註冊模塊的模板文件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') 重複密碼
            input(type='password',placeholder='重複密碼',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='登錄')

最終用戶註冊效果以下:

用戶登陸模塊和這個效果相仿,就不查看了,少了一個重複密碼的input而已。

下面咱們須要編寫用戶註冊的邏輯,在編寫用戶註冊邏輯的前,用戶數據須要持久化,因此首先要安裝MongoDB數據庫在本身的機器上,關於其安裝請參考以前的一篇博文-windows系統安裝MongoDB

MongoDB這種nosql類型的數據庫,很是適合用戶存儲JSON對象類型的數據,有了mongoDB,就能夠免去數據庫表設計部分的工做,對比之前使用的mysql,sqlserver以及oracle仍是很是方便的。關於mongoDB數據庫的熟悉和學習,推薦其官網,官網詳細介紹了該數據庫的一切。英文很差能夠去中文社區。同時爲了使用nodejs來操做mongoDB數據庫,咱們使用mongoose這個對象模型,它是將mongoDB中的一個集合映射爲nodejs中的一個model,而後在該model上提供操做這個集合的一些方法,使用它就能夠避免咱們本身利用nodejs提供的原生操做mongoDB數據庫的語法去手寫數據庫CURD的方法,大大見曬了工做量,提升了開發效率。關於mongoose的學習,推薦去其官網,裏面詳述了它的安裝,使用以及API調用狀況。

解決了mongoDB安裝和操做問題,咱們來對數據庫操做的model類,首先在項目路徑下創建一個db.js文件,用來鏈接數據庫並對數據庫進行全局配置,以下

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文件,爲了數據庫便於靈活修改,咱們將某些信息存儲在配置文件中。而後加在了以前安裝的mongoose模塊,而後調用該模塊的connect方法來鏈接咱們配置的數據庫,而後將鏈接以對象的形式返回供外部調用。

settings.js

module.exports={
    "ip":"localhost",
    "db":"microblog",
    "host":27071
};

MongoDB的默認端口是27071,通常可使用默認端口便可,數據庫鏈接大時候能夠不指定端口,數據庫名爲microblog.

而後以db.js返回的數據庫鏈接對象爲基礎,咱們在項目根目錄下建立一個models文件夾,用來存放數據模型。建立一個user.js映射咱們數據庫中的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中定義的鏈接對象,並以該對象爲基礎構造一個Schema(架構),mogoose操做數據庫是以架構爲基礎的,相似於咱們其餘ORM模型中屬性和方法的定義。這裏咱們定義了一個架構,擁有兩個屬性,name和password,都是string類型,對應用戶的用戶名和密碼。而後利用該架構去建立一個model,該model上定義了對數據集合的增刪改查等方法,不用咱們本身再去定義和編寫其餘代碼。在原書中這一節是利用node.js操做MongoDB數據庫的原生API去定義了一個user對象,而後在user對象上自定義了一些CRUD的方法。能夠看出,直接使用Mongoose能夠大大減小開發量而且擁有更好的效率和性能。

到目前爲止,咱們已經有了界面(view),數據模型(model),就差邏輯代碼(controller)沒有編寫了。在編寫邏輯代碼以前須要先說下express框架的特色以及它的總體運行方式。因爲本人使用過一些相似的如Asp.net mvc,Yii以及thinkphp等MVC框架,使用express以後最大的感受是這個框架夠輕量級,尤爲是express4.X以後,它僅僅保留了靜態文件路徑映射模塊做爲該框架自己的內置模塊,其餘的功能都以中間件的形式採用require(modulename)進行引入,只有引入後纔可以使用該模塊提供的功能。

express的工做原理是客戶端發送一個request,express接到該請求,能夠將它進行處理以後傳遞給其餘中間件進行處理,最終處理完成以後,採用respond.end或者response.render進行頁面渲染或響應,進行頁面渲染的時候,採用參數傳遞頁面須要的數據給對應模板引擎,模板引擎收到數據而後按照本身的語法進行替換生成對應的html,最終返回給瀏覽器進行渲染。

在express中,最關鍵的部分就是路有機制,咱們全部基於請求作出的響應都是對該路由進行監聽捕獲的結果。舉個例子,若是咱們請求一個路徑爲http://localhost:3000/user,那麼必須在routes文件夾下面的路徑監聽(暫且叫作監聽吧)的js文件中編寫對該請求的響應代碼,諸如app.post('/user',function(...){...})之類的代碼,若是不存在這樣的代碼,就會報一個404錯誤,由於請求沒有獲得響應,express實例不知道怎麼去響應這個請求。以上就是express大體的原理和工做流程,對於它的學習,推薦去express官網直接去看文檔,講的很詳細。

如今回到用戶註冊模塊,咱們註冊用戶常見的作法是註冊成功以後就默認用戶已經登陸,直接跳轉到歡迎登錄界面。在這裏咱們須要將用戶數據在註冊成功以後保存在session中,express框架對於session的支持是經過中間件express-session來的,使用方式依然是在npm 下安裝,而後在項目主文件中使用require加載,最後調用其提供的API,爲了使用session,必須先安裝cookie的支持,這裏利用cookie-parser這個中間件來爲express框架提供cookie支持,它的具體使用方式能夠去上面提供的地址自行查看。對於session,咱們常見框架的作法是在服務器端將其存放到文件當中,因爲這裏咱們有了MongoDB數據庫,更理想的狀態是將它存在數據庫中,這樣能夠更靈活去控制。使用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數據庫中,secret屬性是對session的簽名,一般是一個字符串,這是必選項,若是不寫,

是沒法完成將session存儲進入數據庫的,關於該功能的更詳細介紹請查看文檔,最後兩句app.use('/',routes)和app.use('/users',users)表明對於這兩個路由的訪問處理代碼咱們封裝在了routes和users模塊中,er這兩個模塊都在routes文件夾下面。

完成了模塊引入加載和一些基本的設置,如今來編寫用戶註冊的邏輯代碼,上面說到對於路徑/的訪問處理在routes模塊中,這個模塊指的就是routes文件夾下面的index.js,部分代碼以下:

  1 var express = require('express');
  2 var crypto = require('crypto');
  3 var router = express.Router();
  4 var db=require('../db');
  5 var User=require('../models/user');
  6 var Post=require('../models/post');
  7 /* GET home page. */
  8 router.get('/', function(req, res, next) {
  9     Post.find({},function(err,posts){
 10         if(err){
 11             req.session.message=err.message;
 12             return res.redirect('/');
 13         }
 14         res.render('index',{
 15             posts:posts
 16         });
 17     });
 18 
 19 });
 20 //發表微博
 21 router.post('/post',function(req, res, next){
 22     var currentUser=req.session.user;
 23     var post=new Post({
 24         user:currentUser.name,
 25         post:req.body.article,
 26         updated:getTime(new Date())
 27     });
 28     post.save(function(err){
 29         if(err){
 30             req.session.message=err.message;
 31             return  res.redirect('/reg');
 32         }
 33         req.session.success="發表成功";
 34         res.redirect('/users/'+currentUser.name);
 35     });
 36 
 37 
 38 });
 39 
 40 function getTime(date){
 41    return date.getFullYear()+
 42     "-"+date.getMonth()+1+"-"+
 43     date.getDate()+" "+
 44     date.getHours()+":"+
 45     date.getMinutes();
 46 }
 47 router.get('/reg', isLogin);
 48 //用戶進入註冊頁面
 49 router.get('/reg',function(req,res){
 50     res.render('reg',{title:"用戶註冊"});
 51 });
 52 router.post('/reg', isLogin);
 53 //用戶點擊註冊按鈕
 54 router.post('/reg',function(req,res){
 55    if(req.body['password']!= req.body['passwordconf']){
 56        req.session.error="兩次密碼不一致";
 57        return res.redirect('/reg');
 58    }
 59     var md5=crypto.createHash('md5');
 60     var password=md5.update(req.body.password).digest('base64');
 61     var newUser=new User({
 62         name:req.body['username'],
 63         password:password
 64     });
 65     User.findOne({name:newUser.name},function(err,user){
 66         if(user){
 67             err="用戶名已經存在";
 68         }
 69         if(err){
 70            req.session.error=err;
 71            return  res.redirect('/reg');
 72         }
 73         newUser.save(function(err){
 74             if(err){
 75                 req.session.error=err.message;
 76                 return  res.redirect('/reg');
 77             }
 78             req.session.user=newUser;
 79             req.session.success="註冊成功";
 80             res.redirect('/');
 81         });
 82     });
 83 });
 84 router.get('/login',isLogin);
 85 router.get('/login',function(req,res){
 86     res.render('login',{title:"用戶登錄"});
 87 });
 88 router.post('/login',isLogin);
 89 router.post('/login',function(req,res){
 90     var md5=crypto.createHash('md5');
 91     var password=md5.update(req.body.password).digest('base64');
 92     User.findOne({name:req.body.username},function(err,user){
 93         if(!user){
 94             req.session.error="用戶不存在";
 95             return res.redirect('/login');
 96         }
 97         if(user.password!=password){
 98             req.session.error="密碼錯誤";
 99             return  res.redirect('/login');
100         }
101             req.session.user=user;
102             req.session.success="登陸成功";
103             res.redirect('/');
104     });
105 });
106 router.get('/logout',function(req,res){
107     req.session.user=null;
108     res.redirect('/');
109 });
110 function isLogin(req,res,next){
111     if(req.session.user){
112         req.session.message="用戶已登陸";
113         return res.redirect('/');
114     }
115     next();
116 }
117 module.exports = router;

上述代碼1-6行都是對外部模塊的引入,8-19行是對首頁路由/的處理代碼。117行將該模塊定義爲router供外部調用。咱們主要看54-83行,這些代碼就是用戶註冊的代碼,54行監聽來自用戶對於/reg路由的post請求,首先判斷兩次密碼是否一致,若是不一致在session中存儲一個錯誤信息而後跳轉到到當前頁面顯示錯誤信息,該錯誤信息供模板引擎顯示給用戶。若是兩次密碼一致首先對密碼進行md5加密,使用的是nodejs提供的核心模塊crypto,並生成一個對象模型User,該對象模型是mongoose中提供的一個model的實例,mongoose在它上面定義了一些操做數據庫的方法。而後調用這個實例的findOne方法檢測該用戶是否已經存在,若是存在就保存錯誤信息到session並跳轉到當前頁顯示錯誤。若是不存在這樣一個用戶就使用save方法進行用戶信息保存,註冊成功後將用戶信息保存在session中,並保存一個success的提示信息,而後跳轉到首頁。這裏須要注意一個坑,之前作php或者.net的時候,咱們一般都是先查詢數據庫等數據庫返回結果提示用戶是否存在以後再進行用戶的save而後在跳轉,這是一種同步方式,跳轉操做須要等待findOne操做返回結果以後才能進行。而nodejs中採用異步IO,最後的跳轉操做須要放在findOne操做的回調函數中進行,跳轉操做沒必要等待findone操做結束後執行,二者是異步的。若是將最後的redirect操做放在findOne操做外部而不是回調函數中,你會在控制檯上獲得一個Can't set headers after they are sent的錯誤,這是由於在fineOne以及save操做以前已經進行行了跳轉,response響應已經結束,不可以重複響應請求。

到目前爲止,用戶註冊模塊基本上已經差很少完成了,最後須要說一下如何在頁面上顯示提示信息或者錯誤信息,以前咱們將提示信息或者錯誤信息都保存在了session中,jade要顯示錯誤信息,它是不可以直接訪問session的,在express2.X即原書中是利用req.flash API+動態視圖助手來實現的,就是發生錯誤的時候先將其利用req.flash方法存儲下來,而後利用動態視圖助手結合模板去渲染給用戶。express4.X廢棄了這種方式,咱們能夠利用req.flash 的原理來本身模擬一個這種機制,同時利用res.locals變量被保存起來,模板在渲染的時候是可以訪問到服務端這個變量的。關於res.locals的更多介紹請查看文檔

爲了模擬這種req.flash機制,咱們在項目入口文件app.js(項目根目錄下)添加一段代碼以下:

 1 app.use(function(req,res,next){
 2 //    res.locals.user=req.session.user;
 3     var err=req.session.error;
 4     var success=req.session.success;
 5     var user=req.session.user;
 6     var mess=req.session.message;
 7     delete req.session.success;
 8     delete  req.session.error;
 9     delete  req.session.message;
10     if(err){
11         res.locals.message="*"+err;
12     }
13     if(mess){
14         res.locals.message="*"+mess;
15     }
16     if(success){
17         res.locals.success=success;
18     }
19     if(user){
20         res.locals.user=user.name;
21     }
22     next();
23 });

這段代碼的意思是用戶請求和響應的時候,捕獲session中存儲的錯誤信息和用戶提示,將其存儲在response.locals變量中,這樣模板就可以獲取。對於錯誤信息和提示,因爲只使用一次,存儲後當即使用delete刪除,對於用戶信息,須要持久保存下來,則不刪除。

這樣,就可以顯示用戶提示或者錯誤信息。

下面演示一下完整的用戶註冊流程以及錯誤信息提示。

當用戶名存在或密碼不一致時,

當註冊成功後跳轉到首頁並顯示用戶註冊成功

同時對於註冊成功和登錄成功擁有不一樣提示,若是該用戶已是登陸狀態則顯示退出和發表文章按鈕,若是沒有登陸,則顯示的是登錄和當即註冊按鈕。

以上就是利用nodejs及express,mongoose,mongoDB,jade進行web開發的主要流程,因爲該項目是對nodejs web開發指南一書中微博項目的重構,因此完整版的項目代碼還有用戶權限控制(已登陸用戶不可以註冊或登錄並提示),用戶進入我的頁面發佈微博並列表顯示,同時首頁顯示最近發佈的微博信息等功能。完整版代碼點這裏,因爲篇幅以及時間問題,上述要點不可能一一展開討論,本文就做爲一個提綱,是對nodejs web開發的一個綜述。

相關文章
相關標籤/搜索