【深刻淺出Node.js系列十一】Node.js開發框架Express4.x

#0 系列目錄#javascript

#1 創建項目# 讓咱們從頭開始Express4.x的安裝和使用吧,安裝Node和NPM在本文就很少說了。Linux環境安裝請參考文章,Node.js&NPM的安裝與配置,Window環境安裝直接下載Node的安裝文件,雙擊安裝就好了。css

首先,咱們須要安裝express庫。在Express3.6.x以前的版本,Express須要全局安裝的,項目構建器模塊是合併在Express項目中的,後來這個構建器被拆分出來,獨立成爲了一個項目express-generator,如今咱們只須要全局安裝express-generator項目就好了html

~ npm install -g express-generator@4  #全局安裝-g
C:\Users\Administrator\AppData\Roaming\npm\express -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\express-ge
nerator\bin\express

express-generator@4.11.2 C:\Users\Administrator\AppData\Roaming\npm\node_modules\express-generator
├── sorted-object@1.0.0
├── commander@2.6.0
└── mkdirp@0.5.0 (minimist@0.0.8)

安裝好express-generator包後,咱們在命令行就可使用express命令了。前端

~ express -V # 檢查express的版本
4.11.2

~ express -h  # 檢查看express的幫助命令
  Usage: express [options] [dir]
  Options:
    -h, --help          output usage information
    -V, --version       output the version number
    -e, --ejs           add ejs engine support (defaults to jade)
        --hbs           add handlebars engine support
    -H, --hogan         add hogan.js engine support
    -c, --css   add stylesheet  support (less|stylus|compass) (defaults to plain css)
        --git           add .gitignore
    -f, --force         force on non-empty directory

接下來,咱們使用express的命令,來建立項目了。java

~ cd D:\workspace\javascript  # 進入工做目錄

~ D:\workspace\javascript>express -e nodejs-demo  # 建立項目
   create : nodejs-demo
   create : nodejs-demo/package.json
   create : nodejs-demo/app.js
   create : nodejs-demo/public/javascripts
   create : nodejs-demo/public/images
   create : nodejs-demo/public
   create : nodejs-demo/public/stylesheets
   create : nodejs-demo/public/stylesheets/style.css
   create : nodejs-demo/views
   create : nodejs-demo/views/index.ejs
   create : nodejs-demo/views/error.ejs
   create : nodejs-demo/routes
   create : nodejs-demo/routes/index.js
   create : nodejs-demo/routes/users.js
   create : nodejs-demo/bin
   create : nodejs-demo/bin/www

   install dependencies:
     $ cd nodejs-demo && npm install
   run the app:
     $ DEBUG=nodejs-demo:* ./bin/www

進入項目目錄,下載依賴庫,構建項目。node

~ D:\workspace\javascript>cd nodejs-demo && npm install

啓動項目。jquery

~ D:\workspace\javascript\nodejs-demo>npm start

> express4-demo@0.0.0 start D:\workspace\javascript\nodejs-demo
> node ./bin/www

module.js:338
    throw err;
          ^
Error: Cannot find module './routes/users'
    at Function.Module._resolveFilename (module.js:336:15)
    at Function.Module._load (module.js:278:25)
    at Module.require (module.js:365:17)
    at require (module.js:384:17)
    at Object. (D:\workspace\javascript\nodejs-demo\app.js:9:13)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Module.require (module.js:365:17)

第一次啓動發生了錯誤,多是express-generator和express不匹配形成的,找到問題在app.js文件中,註釋第9行和第26行。git

..
//var users = require('./routes/users');
..
//app.use('/users', users);       
..

再次啓動項目。web

D:\workspace\javascript\nodejs-demo>npm start
> express4-demo@0.0.0 start D:\workspace\javascript\nodejs-demo
> node ./bin/www

項目啓動成功,打開瀏覽器 http://localhost:3000,就能夠看到顯示的頁面了。redis

輸入圖片說明

#2 目錄結構# 接下來,咱們詳細看一下Express4項目的結構、配置和使用。

bin, 存放啓動項目的腳本文件

node_modules, 存放全部的項目依賴庫。

public,靜態文件(css,js,img)

routes,路由文件(MVC中的C,controller)

views,頁面文件(Ejs模板)

package.json,項目依賴配置及開發者信息

app.js,應用核心配置文件

輸入圖片說明

#3 package.json項目配置# package.json用於項目依賴配置及開發者信息,scripts屬性是用於定義操做命令的,能夠很是方便的增長啓動命令,好比默認的start,用npm start表明執行node ./bin/www命令。查看package.json文件。

{
  "name": "express4-demo",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.10.2",
    "cookie-parser": "~1.3.3",
    "debug": "~2.1.1",
    "ejs": "~2.2.3",
    "express": "~4.11.1",
    "morgan": "~1.5.1",
    "serve-favicon": "~2.2.0"
  }
}

#4 app.js核心文件# 從Express3.x升級到Express4.x,主要的變化就在app.js文件中。查看app.js文件,我已經增長註釋說明。

// 加載依賴庫,原來這個類庫都封裝在connect中,如今需地注單獨加載
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 app = express();

// 定義EJS模板引擎和模板文件位置,也可使用jade或其餘模型引擎
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// 定義icon圖標
app.use(favicon(__dirname + '/public/favicon.ico'));
// 定義日誌和輸出級別
app.use(logger('dev'));
// 定義數據解析器
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// 定義cookie解析器
app.use(cookieParser());
// 定義靜態文件目錄
app.use(express.static(path.join(__dirname, 'public')));

// 匹配路徑和路由
app.use('/', routes);
//app.use('/users', users);

// 404錯誤處理
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// 開發環境,500錯誤處理和錯誤堆棧跟蹤
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// 生產環境,500錯誤處理
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});

// 輸出模型app
module.exports = app;

咱們看到在app.js中,原來調用connect庫的部分都被其餘的庫所代替,serve-favicon、morgan、cookie-parser、body-parser,默認項目中,只用到了最基本的幾個庫,尚未其餘須要替換的庫,在本文最後有詳細列出。

另外,原來用於項目啓動代碼也被移到./bin/www的文件,www文件也是一個node的腳本,用於分離配置和啓動程序。查看./bin/www文件。

#!/usr/bin/env node   

/**
 * 依賴加載
 */
var app = require('../app');
var debug = require('debug')('nodejs-demo:server');
var http = require('http');

/**
 * 定義啓動端口
 */
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * 建立HTTP服務器實例
 */
var server = http.createServer(app);

/**
 * 啓動網絡服務監聽端口
 */
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * 端口標準化函數
 */
function normalizePort(val) {
  var port = parseInt(val, 10);
  if (isNaN(port)) {
    return val;
  }
  if (port >= 0) {
    return port;
  }
  return false;
}

/**
 * HTTP異常事件處理函數
 */
function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * 事件綁定函數
 */
function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

#5 Bootstrap界面框架# 建立Bootstrap界面框架,直接在index.ejs文件上面作修改。能夠手動下載Bootstrap庫放到項目中對應的位置引用,也能夠經過bower來管理前端的Javascript庫,參考文章 bower解決js的依賴管理另外還能夠直接使用免費的CDN源加載Bootstrap的css和js文件。下面我就直接使用bower來管理前端的JavaScript庫的方式。編輯views/index.ejs文件:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <title><%= title %></title>
    <link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <div class="well jumbotron">
      <h1><%= title %></h1>
      <p>This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p>
      <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
    </div>
    <script src="/bower_components/jquery/dist/jquery.min.js"></script>
    <script src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
  </body>
</html>

效果以下,已經加入了bootstrap的樣式了。

輸入圖片說明

接下來,咱們把index.ejs頁面切分紅3個部分:header.ejs, index.ejs, footer.ejs,用於網站頁面的模塊化

header.ejs, 爲頁面的頭部區域

index.ejs, 爲內容顯示區域

footer.ejs, 爲頁面底部區域

編輯header.ejs:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <title><%= title %></title>
    <link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>

編輯footer.ejs:

<script src="/bower_components/jquery/dist/jquery.min.js"></script>
    <script src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
  </body>
</html>

編輯index.ejs:

<% include header.ejs %>

<div class="well jumbotron">
<h1><%= title %></h1>
<p>This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p>
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
</div>

<% include footer.ejs %>

把頁表和頁底的代碼分離後,讓index.ejs頁面的核心代碼更少,更容易維護。

#6 路由功能# 路由功能,是Express4之後全面改版的功能。在應用程序加載隱含路由中間件,不用擔憂在中間件被裝載相對於路由器中間件的順序。定義路由的方式是不變的,路由系統中增長2個新的功能。

app.route()函數,建立可連接的途徑處理程序的路由路徑。

express.Router類,建立模塊化安裝路徑的處理程序。

app.route方法會返回一個Route實例,它能夠繼續使用全部的HTTP方法,包括get,post,all,put,delete,head等。

app.route('/users')
  .get(function(req, res, next) {})
  .post(function(req, res, next) {})

express.Router類,則能夠幫助咱們更好的組織代碼結構。在app.js文件中,定義了app.use(‘/’, routes); routes是指向了routes目錄下的index.js文件,./routes/index.js文件中,express.Router被定義使用,路徑/*處理都會由routes/index.js文件裏的Router來處理。若是咱們要管理不一樣的路徑,那麼能夠直接配置爲多個不一樣的Router。

app.use('/user', require('./routes/user').user);
app.use('/admin', require('./routes/admin').admin);
app.use('/', require('./routes'));

#7 Ejs模板使用# 讓ejs模板文件,使用擴展名爲html的文件。修改:app.js

app.engine('.html', ejs.__express);
app.set('view engine', 'html');
// app.set('view engine', 'ejs');

修改後,ejs變量沒有定義,supervisor的程序會一直報錯

ReferenceError: ejs is not defined
at Object. (D:\workspace\project\nodejs-demo\app.js:17:21)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:901:3
DEBUG: Program node app.js exited with code 8

在app.js中增長ejs變量:

var express = require('express')
, routes = require('./routes')
, user = require('./routes/user')
, http = require('http')
, path = require('path')
, ejs = require('ejs');

#8 Session使用# session這個問題,實際上是涉及到服務器的底層處理方式。像Java的web服務器,是多線程調用模型。每用戶請求會打開一個線程,每一個線程在內容中維護着用戶的狀態。

像PHP的web服務器,是交行CGI的程序處理,CGI是無狀態的,因此通常用cookie在客戶的瀏覽器是維護用戶的狀態。但cookie在客戶端維護的信息是不夠的,因此CGI應用要模仿用戶session,就須要在服務器端生成一個session文件存儲起來,讓本來無狀態的CGI應用,經過中間文件的方式,達到session的效果。

Nodejs的web服務器,也是CGI的程序無狀態的,與PHP不一樣的地方在於,單線程應用,全部請求都是異步響應,經過callback方式返回數據。若是咱們想保存session數據,也是須要找到一個存儲,經過文件存儲,redis,Mongdb均可以。

接下來,我將演示如何經過mongodb來保存session,並實現登錄後用戶對象傳遞。

app.js文件:

var express = require('express')
, routes = require('./routes')
, user = require('./routes/user')
, http = require('http')
, path = require('path')
, ejs = require('ejs')
, SessionStore = require("session-mongoose")(express);

var store = new SessionStore({
    url: "mongodb://localhost/session",
    interval: 120000
});

....
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.cookieSession({secret : 'fens.me'}));

app.use(express.session({
    secret : 'fens.me',
    store: store,
    cookie: { maxAge: 900000 }
}));

app.use(function(req, res, next){
    res.locals.user = req.session.user;
    next();
});
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

注:app.js文件有順序要求,必定要注意!!!

安裝session-mongoose依賴庫:

D:\workspace\project\nodejs-demo>npm install session-mongoose
D:\workspace\project\nodejs-demo\node_modules\session-mongoose\node_modules\mongoose\node_modules\mongodb\node_modules\bson>node "D:\toolkit\nodejs\node_modules\npm\bin\node-gyp-bin\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild
C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Microsoft.Cpp.InvalidPlatform.Targets(23,7): error MSB8007: 項目「kerberos.vcxproj」的平臺無效。平臺爲「x64」。您會看到此消息的可能緣由是,您嘗試在沒有解決方案文件的狀況下生成項目,而且爲
oose\node_modules\mongoose\node_modules\mongodb\node_modules\bson\build\bson.vcxproj]
session-mongoose@0.2.2 node_modules\session-mongoose
└── mongoose@3.6.10 (mpath@0.1.1, ms@0.1.0, hooks@0.2.1, sliced@0.0.3, muri@0.3.1, mpromise@0.2.1, mongodb@1.3.3)

安裝有錯誤可是不要緊。訪問:http://localhost:3000/login,正常

修改routes/index.js文件,exports.doLogin方法:

exports.doLogin = function(req, res){
    var user={
        username:'admin',
        password:'admin'
    }
    if(req.body.username===user.username && req.body.password===user.password){
        req.session.user=user;
        return res.redirect('/home');
    } else {
        return res.redirect('/login');
    }
};

exports.logout方法:

exports.logout = function(req, res){
    req.session.user=null;
    res.redirect('/');
};

exports.home方法:

exports.home = function(req, res){
    res.render('home', { title: 'Home'});
};

這個時候session已經起做用了,exports.home的user顯示傳值已經被去掉了。 是經過app.js中app.use的res.locals變量,經過框架進行的賦值。

app.use(function(req, res, next){
    res.locals.user = req.session.user;
    next();
});

注:這個session是express3.0的寫法,與express2.x是不同的。原理是在框架內每次賦值,把咱們剛纔手動傳值的過程,讓框架去完成了

相關文章
相關標籤/搜索