Express

Express 框架

第一章 Express簡介

npm提供了大量的第三方模塊嘶居,其中不乏許多Web框架麸锉,我們沒有必要重復(fù)發(fā)明輪子条辟,因而選擇使用 Express作為開發(fā)框架媒熊,因為它是目前最穩(wěn)定、使用最廣泛谜酒,而且Node.js官方推薦的唯一一個 Web開發(fā)框架叹俏。
Express是一個輕量級的Web框架,多數(shù)功能只是對HTTP協(xié)議中常用操作的封裝僻族,更多的功能需要插件或者整合其他模塊來完成.

Express中文官網(wǎng) http://www.expressjs.com.cn/

第二章 初始化一個Express應(yīng)用

  1. 我們想要安裝Express,首先需要安裝Epxress生成器,在全局通過命令安裝
npm install express-generator -g
  1. 等待安裝完畢粘驰,查看幫助信息:
express --help

可以看到屡谐,

Usage: express [options] [dir]

我們可以用express 在當(dāng)前目錄創(chuàng)建dir,并且依據(jù)配置選項
我們可以通過option設(shè)置express使用的模板蝌数,這里我們用ejs愕掏,也可以設(shè)置css的引擎,比如less和sass顶伞,這里我們先使用原生的css
所以只需要輸入

express myapp --view ejs

express會給我們快速的搭建一個app框架饵撑,然后根據(jù)提示進入myapp文件夾,運行

npm install

npm會自動幫助我們安裝當(dāng)前應(yīng)用所需要的模塊唆貌,等待安裝完畢之后滑潘,啟動:

npm start

瀏覽器打開:"localhost:3000",可以看到express框架給我返回的信息。

在調(diào)試過程中,如果我們不想反復(fù)的重啟服務(wù)端,可以npm安裝nodemon,使用nodemon啟動express項目

第三章 目錄分析

回到我們安裝express之前,看一下express快速搭建的目錄結(jié)構(gòu):

 create : taobao
   create : taobao/package.json //依賴關(guān)系文件
   create : taobao/app.js // 入口文件
   create : taobao/public //公共資源文件夾
   create : taobao/public/images
   create : taobao/public/stylesheets
   create : taobao/public/stylesheets/style.css
   create : taobao/public/javascripts
   create : taobao/views //視圖文件夾
   create : taobao/views/index.ejs
   create : taobao/views/error.ejs
   create : taobao/routes //路由文件夾
   create : taobao/routes/index.js
   create : taobao/routes/users.js
   create : taobao/bin
   create : taobao/bin/www //配置文件

由此可見,express是一個標(biāo)準(zhǔn)的MVC框架,通過路由分配視圖,并且可以在路由中寫上我們的控制結(jié)構(gòu).
修改 routes下的index.js

router.get('/', function(req, res, next) {
  res.render('index', { title: '你好express' });
});

發(fā)現(xiàn)我們的頁面出現(xiàn)了改變,這個路由跟我們自己寫的路由作用是差不多的,具體用法我們后邊再談.

第四章 模板的使用方式

我們在安裝express的時候,使用的模板是ejs,具體什么是模板呢?ejs又怎么用呢?
簡單來講,模板就是幫助我們在view層進行輸出的工具.我們打開view下的index.ejs,可以看到,這個模板其實就是在ejs下寫了我們常見的html代碼,只是多了幾個不一樣的標(biāo)簽:

<!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 %>可以幫助我們將路由中的{ title: '你好express' }輸出到模板當(dāng)中,其中的render就是模板渲染的方法,幫助我們渲染第一個參數(shù)index所對應(yīng)的模板.類似angular,我們也可以在標(biāo)簽內(nèi)部寫自己的表達式

<h1><%= title.toUpperCase() %></h1>

4.1 模板引擎的用法

ejs有三種常用的標(biāo)簽

  1. <% code %>:運行 JavaScript 代碼锨咙,不輸出
  2. <%= code %>:顯示轉(zhuǎn)義后的 HTML內(nèi)容
  3. <%- code %>:顯示原始 HTML 內(nèi)容

注意:<%= code %><%- code %>都可以是JavaScript表達式生成的字符串语卤,當(dāng)變量code為普通字符串時,兩者沒有區(qū)別酪刀。當(dāng) code 比如為 <h1>hello</h1> 這種字符串時粹舵,<%= code %> 會原樣輸出 <h1>hello</h1>,而 <%- code %> 則會顯示H1大的hello字符串

下面的例子可以幫我們解釋<% code %>的用法:

index.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <ul>
    <% for(var i=0; i<contents.length; i++) { %>
        <li><%= contents[i] %></li>
    <% } %>
    </ul>
  </body>
</html>

index.js

router.get('/', function(req, res, next) {
    res.render('index',{ 
        title: 'express',
        contents:['AngularJS','Node.js','Express']
    });
});

查看渲染之后的結(jié)果.
同樣,如果你想要動態(tài)的調(diào)整js代碼,可以將js代碼在<script>標(biāo)簽中按照原樣渲染.

4.3 渲染模板的方法

我們剛剛看到,routes下的index.js使用render可以將后邊的參數(shù)渲染到模板之中,但是,我們在views下無法看到users.ejs,這是因為在user.js下使用了send方法,可以將后邊的信息直接渲染而不通過模板.

4.4 includes

我們使用模板引擎通常不是一個頁面對應(yīng)一個模板骂倘,這樣就失去了模板的優(yōu)勢齐婴,而是把模板拆成可復(fù)用的模板片段組合使用,如在 views 下新建 header.ejs 和 footer.ejs稠茂,并修改users.ejs

views/header.ejs

<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
      body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;}
    </style>
  </head>
  <body>

views/footer.ejs

  </body>
</html>

views/users.ejs

<%- include('header') %>
  <h1><%= name.toUpperCase() %></h1>
  <p>hello, <%= name %></p>
<%- include('footer') %>

routes/users.js

router.get('/', function(req, res, next) {
  // res.send('respond with a resource');
  res.render('users',{ 
        name: 'express'
    });
});

訪問'localhost:3000/users'可以看到渲染后的結(jié)果.
我們將users.ejs拆成出了header.ejs和footer.ejs,并在users.ejs通過 ejs 內(nèi)置的 include方法引入柠偶,從而實現(xiàn)了跟以前一個模板文件相同的功能.

注意:要用 <%- include('header') %> 而不是 <%= include('header') %>

第五章 路由

我們在路由中將數(shù)據(jù)渲染并且分配到模板當(dāng)中,那么我們?nèi)绾螌懸粋€路由呢?

5.1 入口文件

首先,我們先要了解入口文件的組成,打開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 index = require('./routes/index');
var users = require('./routes/users');

路由都是模塊寫法,而入口文件將這些路由引入,我們就可以直接使用了.

app.use('/', index);
app.use('/users', users);

這則使用了我們的路由

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

這兩句話設(shè)置了我們的模板引擎,如果我們想要使用sass或者less等css引擎,同樣可以向下追加.

// catch 404 and forward to error handler
...
// error handler
...

剩下的部分則是規(guī)定了404和500錯誤下的頁面渲染方法.
由此,我們可以在routes下添加的路由文件,并且在入口文件引入

5.2 新增路由

在routes下添加新的路由:
routers/tests.js

var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
   res.send('這是新的路由');
});
module.exports = router;

同樣,在入口文件添加
app.js

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

訪問'localhost:3000/tests'則可以獲取我們新增加的路由信息.

5.3 路由中的參數(shù)

其中 req,res是我們熟悉的請求和響應(yīng)對象,當(dāng)我們的地址欄訪問模板的時候,可以獲取參數(shù)

ruoter.get('/:name', function(req, res, next) {
  res.send('hello, ' + req.params.name);
});

我們在地址欄請求'localhost:3000/tests/Y18',我們可以看到響應(yīng)信息

hello, Y18

不難看出:req 包含了請求來的相關(guān)信息,res 則用來返回該請求的響應(yīng)睬关。下面介紹幾個常用的

req 的屬性:

  • req.query: 解析后的 url 中的 querystring诱担,如 ?name=haha,req.query 的值為 {name: 'haha'}
  • req.params: 解析 url 中的占位符电爹,如 /:name蔫仙,訪問 /haha,req.params 的值為 {name: 'haha'}
  • req.body: 解析后請求體丐箩,需使用相關(guān)的模塊摇邦,如 body-parser,請求體為 {"name": "haha"}屎勘,則 req.body 為 {name: 'haha'},我們可以用接受post請求為例.
  • 注意施籍,無論是send或者render,我們只能獲取第一次符合標(biāo)準(zhǔn)的渲染頁面

5.4 next和中間件

前面我們講解了express中路由和模板引擎ejs的用法概漱,但express的精髓并不在此丑慎,在于中間件的設(shè)計理念。

我們可以看到,在路由的回調(diào)函數(shù)中竿裂,有一個參數(shù)next玉吁,這就是我們使用中間件最重要的部分。
express中的中間件(middleware)是用來處理請求的腻异,當(dāng)一個中間件處理完进副,可以通過調(diào)用 next() 傳遞給下一個中間件,如果沒有調(diào)用 next()悔常,則請求不會往下傳遞影斑,如內(nèi)置的 res.render 其實就是渲染完 html 直接返回給客戶端,如果調(diào)用了next()这嚣,那么請求會繼續(xù)向下傳遞鸥昏,訪問路由組中的下一個路由塞俱。

next中間件分為應(yīng)用級中間件和路由級中間件

應(yīng)用級中間件

我們回頭再來看一下入口文件:

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('/', index);
app.use('/users', users);
app.use('/tests', tests);

其實,這些app.use()也是給我們加載中間件,只是這些中間件是在應(yīng)用中使用的,應(yīng)用級中間件綁定到 app 對象 使用 app.use() 和 app.METHOD()姐帚, 其中, METHOD 是需要處理的 HTTP 請求的方法障涯,例如 GET, PUT, POST 等等罐旗,全部小寫。我們自定義一下全局中間件:

// 掛載到全局的中間件,任何請求都會執(zhí)行它
app.use(function (req, res, next) {
  console.log('Time:', Date.now());
  next();
});
// 掛載至 /hello/:name 的中間件唯蝶,任何指向 /hello/:name 的請求都會執(zhí)行它
app.use('/hello/:name', function (req, res, next) {
  console.log('Request Type:', req.method);
  next();
});
// 路由和句柄函數(shù)(中間件系統(tǒng))九秀,處理指向 /hello/:name 的 GET 請求
app.get('/hello/:name', function (req, res, next) {
  res.send('hello '+ req.params.name);
});

如果我們不指定路徑,則默認匹配所有路徑,我們在最后一個路由中并沒有使用next,所以請求沒有向下傳遞.
我們在上方引入的其余全局中間件,即第三方中間件,其實也在回調(diào)函數(shù)的末尾使用了next方法,使得請求能夠繼續(xù)向下傳遞.

在我們訪問'localhost:3000/hello/Y18',可以在控制臺和頁面都能看到數(shù)據(jù)返回.

注意:

作為中間件系統(tǒng)的路由句柄,使得為路徑定義多個路由成為可能粘我。

// 一個中間件棧鼓蜒,處理指向 /user/:id 的 GET 請求
app.get('/hello/:id', function (req, res, next) {
  console.log('ID:', req.params.id);
  next();
}, function (req, res, next) {
  res.send('hello Info');
});

// 處理 /user/:id, 打印出用戶 id
app.get('/hello/:id', function (req, res, next) {
  res.end(req.params.id);
});

但是由于第一個路由已經(jīng)終止了請求-響應(yīng)循環(huán),第二個路由雖然不會帶來任何問題征字,但卻永遠不會被調(diào)用.
如果需要在中間件棧中跳過剩余中間件都弹,調(diào)用 next('route') 方法將控制權(quán)交給下一個路由

// 一個中間件棧,處理指向 /user/:id 的 GET 請求
app.get('/hello/:id', function (req, res, next) {
  console.log('ID:', req.params.id);
  if(req.params.id == 4) next('route');
  else next();
}, function (req, res, next) {
  res.send('hello Info');
});

// 處理 /user/:id匙姜, 打印出用戶 id
app.get('/hello/:id', function (req, res, next) {
  res.end(req.params.id);
});

路由級中間件

路由級中間件和應(yīng)用級中間件一樣畅厢,只是它綁定的對象為 express.Router()

修改index.js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/:name', function(req, res, next) {
    // 如果 user id 為 0, 跳到下一個路由
    if (req.params.name != 'Y18') next('route');
    // 否則將控制權(quán)交給棧中下一個中間件
    else next(); 
}, function (req, res, next) {
    // 渲染常規(guī)頁面
    res.send('你別來了,Y18');
});
// 處理name為Y18的路由氮昧, 渲染一個特殊頁面
router.get('/:name', function (req, res, next) {
  res.send('歡迎你~ '+ req.params.name);
});

module.exports = router;

錯誤處理中間件

當(dāng)我們訪問一個不存在的路由時,雖然我們最后一個路由沒有寫next中間件,但是錯誤路由依舊執(zhí)行,查看源碼:

https://github.com/expressjs/express/blob/master/lib/router/index.js

function next(err) {
    ... //此處源碼省略
    // find next matching layer
    var layer;
    var match;
    var route;

    while (match !== true && idx < stack.length) {
      layer = stack[idx++];
      match = matchLayer(layer, path);
      route = layer.route;

      if (typeof match !== 'boolean') {
        // hold on to layerError
        layerError = layerError || match;
      }

      if (match !== true) {
        continue;
      }
      ... //此處源碼省略
    }
  ... //此處源碼省略
    // this should be done for the layer
    if (err) {
        layer.handle_error(err, req, res, next);
    } else {
      layer.handle_request(req, res, next);
    }
  }

next函數(shù)內(nèi)部有個while循環(huán)框杜,每次循環(huán)都會從stack中拿出一個layer,這個layer中包含了路由和中間件信息袖肥,然后就會用layer和請求的path就行匹配咪辱,如果匹配成功就會執(zhí)行l(wèi)ayer.handle_request,調(diào)用中間件函數(shù)椎组。但如果匹配失敗梧乘,就會循環(huán)下一個layer(即中間件)。

express有成百上千的第三方中間件,在開發(fā)過程中我們首先應(yīng)該去npm上尋找是否有類似實現(xiàn)的中間件选调,盡量避免造輪子夹供,節(jié)省開發(fā)時間。下面給出幾個常用的搜索 npm 模塊的網(wǎng)站:

  1. http://npmjs.com(npm 官網(wǎng))
  2. http://node-modules.com
  3. https://npms.io
  4. https://nodejsmodules.org

第六章 在express中使用less或sass

如果我們想要使用sass或者less,首先要對其進行學(xué)習(xí);

Express支持sass,less等css引擎,我們現(xiàn)在使用sass編譯模板的樣式:
重新安裝一個express應(yīng)用:express app --view ejs -c sass
我們可以看到在入口文件中看到變化:

app.use(require('node-sass-middleware')({
  src: path.join(__dirname, 'public'),
  dest: path.join(__dirname, 'public'),
  indentedSyntax: true,
  sourceMap: true
}));

這其實就是引入了node的sass中間件仁堪。
同時哮洽,在public/stylesheets中可以看到style.sass這個sass文件。
啟動express弦聂,訪問'localhost:8000/'我們可以看到在這個文件夾中生成了兩個新的文件:
'style.css'
'style.css.map'
這就是我們熟悉的sass經(jīng)過編譯后的文件鸟辅。
同樣,我們可以寫自己的sass文件莺葫,決定樣式:
修改style.sass

$fontStack:    Helvetica, sans-serif;
$primaryColor: #8A2BE2;

body
    font-family: $fontStack;
    padding: 50px;
p
  color: $primaryColor;

ul li
     color: #BDB76B;

a
  text-decoration: none;

修改index.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
    <ul>
        <li>AngularJS</li>
        <li>Node.js</li>
        <li>Express</li>
    </ul>
    <a id="test" href="www.zixue.it">自學(xué)it網(wǎng)</a>
  </body>
</html>

訪問'localhost:3000',可以看到我們修改之后的樣式.

我們可以修改indentedSyntax改為false,實現(xiàn)scss方式的書寫

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匪凉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子捺檬,更是在濱河造成了極大的恐慌再层,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堡纬,死亡現(xiàn)場離奇詭異聂受,居然都是意外死亡,警方通過查閱死者的電腦和手機烤镐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門蛋济,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人炮叶,你說我怎么就攤上這事碗旅。” “怎么了镜悉?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵祟辟,是天一觀的道長。 經(jīng)常有香客問我积瞒,道長川尖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任茫孔,我火速辦了婚禮叮喳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缰贝。我一直安慰自己馍悟,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布剩晴。 她就那樣靜靜地躺著锣咒,像睡著了一般侵状。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上毅整,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天趣兄,我揣著相機與錄音,去河邊找鬼悼嫉。 笑死艇潭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的戏蔑。 我是一名探鬼主播蹋凝,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼总棵!你這毒婦竟也來了鳍寂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤情龄,失蹤者是張志新(化名)和其女友劉穎迄汛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刃唤,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡隔心,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年白群,在試婚紗的時候發(fā)現(xiàn)自己被綠了尚胞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡帜慢,死狀恐怖笼裳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情粱玲,我是刑警寧澤躬柬,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站抽减,受9級特大地震影響允青,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卵沉,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一颠锉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧史汗,春花似錦琼掠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悼瓮。三九已至,卻和暖如春艰猬,著一層夾襖步出監(jiān)牢的瞬間横堡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工冠桃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留翅萤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓腊满,卻偏偏與公主長得像套么,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子碳蛋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容