學(xué)習(xí)Node.js全棧框架MEAN-04-Express

Express是Node社區(qū)里的超級明星,他的作者TJ Holowaychuk也因此成為了社區(qū)里大紅大紫的開發(fā)者。Express框架是基于Connect的小型Web服務(wù)常見功能模塊支持的框架。之前,我們只是在sever.js一個文件中開發(fā),但Express會引導(dǎo)我們?nèi)ソ⒔M織更合理更龐大的項(xiàng)目。

安裝Express

還是使用npm:
npm install express
這種導(dǎo)入包的方法存在當(dāng)項(xiàng)目使用了很多包后管理起來很麻煩的缺點(diǎn),所以介紹一種新的方法:package.json配置文件

{
  "name":"MEAN",
  "version":"0.0.1",
  "dependencies":{
    "express":"~4.8.8"
  }
}

配置文件有三個key,其中dependencies代表了以來的包。然后在所在目錄執(zhí)行:
npm install
即可安裝依賴包

第一個Express工程

在 npm install 了 package.json后,新建一個server.js文件。

var express = require('express');
var app = express();
//裝配中間件
app.use('/',function (req,res) {
   res.send('Hello Express');
});
app.listen(3000);
console.log('Server running at http://localhost:3000/')
//導(dǎo)出
module.exports = app;

上代碼就是一個簡單地Express工程,有以下幾點(diǎn)留意:

  • app.use( ),沿用了Connect的方式,注冊middleware
  • res.send( )方法集成了設(shè)置Content-Type,和res.end( )方法
  • module.exports方法導(dǎo)出app,以便接下來使用

When passing a buffer to the res.send( ) method, the Content-Typeheader will be set to application/octet-stream. When passinga string, it will be set to text/html and when passing an object or anarray, it will be set to application/json.

解讀Application、Request 和Response

這三個實(shí)例是Express里最常用的。

Application

Application 實(shí)例有以下常用方法,幫助開發(fā)者配置web服務(wù):

  • app.set(name ,value):設(shè)置環(huán)境變量
  • app.get(name):獲取環(huán)境變量
  • app.engine(ext, callback):設(shè)置模板引擎來渲染文件。比如使用EJS模板引擎來模板化HTML文件:app.('html',require('ejs').renderFile)
  • app.use([path], callback):創(chuàng)建 Express middleware來處理HTTP請求。
  • app.VERB(path, [callback...], callback):定義一個或多個middleware,和例如GET這樣的verb相關(guān)
  • app.route(path).VERB([callback...], callback): 定義一個或多個middleware,以及他們對應(yīng)的verb和路徑
  • app.param([name], callback):給一個請求所對應(yīng)的方法綁定路由參數(shù)。

Request

處理http請求的實(shí)例,以下是常用的變量/方法

  • req.query:包含了query-string變量
  • req.params:包含了路徑變量
  • req.body:檢索請求body,這個變量也包含該在bodyParser( )中間件中
  • req.param(name):檢索一個請求參數(shù)值
  • req.path,req.host,req.ip: 請求的路徑/主機(jī)/IP
  • req.cookies: 和中間件cookieParser( )一起使用,來檢索用戶發(fā)出的cookie

Response

處理HTTP響應(yīng),常用方法:

  • res.status(code):設(shè)置響應(yīng)HTTP狀態(tài)碼
  • res.set(field [value]):設(shè)置響應(yīng)頭 HTTP header
  • res.cookie (name, value, [option] ):設(shè)置響應(yīng)cookie,option參數(shù)用來配置cookie,例如maxAge
  • res.redirect([status], url):再次定向請求一個新的URL,同時可以傳遞一個HTTP狀態(tài)碼,如果不設(shè)置,默認(rèn)302
  • res.send([body|status], [body]):用于non-streaming 響應(yīng)。這個方法同時做了諸如設(shè)置Content-Type、Content-Length 頭和緩存頭的操作。
  • res.json([status|body], [body]):和res.send( )一樣,發(fā)送object或者array。多數(shù)時候用作語法糖,但是有些時候比如強(qiáng)制發(fā)送一個空J(rèn)SON響應(yīng)。
  • res.render(view, [locals], callback):渲染一個view或發(fā)出一個HTML響應(yīng)

外部middleware

  • Morgan:HTTP請求logger
  • body-parser:處理請求body,支持多種請求類型
  • method-override:支持HTTP verb例如PUT DELETE
  • Compression:gzip/deflate壓縮相應(yīng)數(shù)據(jù)
  • express.static:處理靜態(tài)文件
  • cookie-parser:包含于res.cookies實(shí)例
  • Session:持久化session

MVC

Express 是patten agnostic(模式不可知論者??不懂);也就意味著,Express不支持任何預(yù)定義語法。在Express中實(shí)現(xiàn)MVC可以通過工程文件夾結(jié)構(gòu)來實(shí)現(xiàn)。

項(xiàng)目文件結(jié)構(gòu)

水平布局

水平布局是從功能這一維度維度對文件進(jìn)行分組整理。文件會依據(jù)其在項(xiàng)目中的功能進(jìn)行分組,一般來說所有的文件都放到一個main application文件夾,然后分為MVC結(jié)構(gòu)(controllers文件夾放所有的控制器,model文件夾放所有的數(shù)據(jù)model...)例如下圖所示:

文件的結(jié)構(gòu)如下:

  • app文件夾是存放Express應(yīng)用邏輯的文件夾,依據(jù)MVC的模式又分為以下子文件夾
    • Controllers文件夾存放應(yīng)用的控制器
    • models存放model
    • routes存放路由中間件middleware
    • views存放界面view
  • config文件夾存放Express應(yīng)用的配置文件,當(dāng)需要加入更多的module的時候,每一個model的配置文件也放到這里。目前這個文件夾有以下幾個子目錄:
    • env存放環(huán)境配置文件
    • config.js存放整個應(yīng)用的配置
    • express.js存放Express的初始化配置
  • public 文件夾存放靜態(tài)客戶端文件,按照MVC的方式又有以下子目錄:
    • config目錄存放AngularJS應(yīng)用的配置文件
    • Controllers存放控制器
    • css放css(有點(diǎn)廢話的感覺O(∩_∩)O~)
    • directives 存放AngularJS的指示
    • filters存放AngularJS的過濾器
    • img放圖片
    • views 放AngularJS的view
    • Application.js 用來初始化AngularJS應(yīng)用
  • package.json 用來管理項(xiàng)目的依賴包
  • server.js是Node.js的main文件,用來加載express.js文件,是Express應(yīng)用啟動的文件

水平項(xiàng)目目錄結(jié)構(gòu)很適合比較小的項(xiàng)目,各個模塊用起來比較方便,也可以清晰的看到每個功能模塊的結(jié)構(gòu)。但是當(dāng)項(xiàng)目復(fù)雜的時候,這種目錄結(jié)構(gòu)就會顯得非常臃腫,hold不住眾多的功能模塊。大項(xiàng)目還是用垂直文件結(jié)構(gòu)比較合適。垂直結(jié)構(gòu)就不詳細(xì)介紹了,看一下下面這張圖應(yīng)該也就明白了:

水平結(jié)構(gòu)

接下來,使用水平結(jié)構(gòu)創(chuàng)建了一個小項(xiàng)目,目錄如下,在根目錄創(chuàng)建并編輯package.json:

Project

然后,在app/controller文件夾,創(chuàng)建一個index.server.controller.js文件,敲入以下代碼:

exports.render = function (req,res) {
    res.end('Hello World');
};

以上代碼,是一個Express controller,使用CommonJS 模塊定義了一個function叫做render( )。當(dāng)定義了一個controller后,需要在Express routing(路由)使用它。

處理請求路由

一般使用Express 的 app.route(path).VERB(callback)或者app.VERB(path,callback)方法來處理路由。方法中的VERB在實(shí)際使用的收替換成小寫的HTTP動詞(get,post),例子:

app.get('/',function(res,req){
    res.send('This is a GET request')
  });
app.post('/',function(res.req){
  res.send('This is a POST request');
});

Express也支持通過定義一個路由然后連接一個或多個middleware的方式來實(shí)現(xiàn)處理多種方式的請求,例如:

app.route('/')get(function(req,res){
  res.send('This is a GET request');
}).post(function(res,req){
  res.send('This is a POST request');
});

Express還有一個很好用的功能就是,在一個路由定義中連接幾個middleware。這意味著middleware可以按照順序被依次調(diào)用。在執(zhí)行請求前做驗(yàn)證。例子:

var express = require('express');

var hasName= function (req,res,next) {
    if(req.param('name')){
        next();
    }else{
        res.send('What is your name?');
    }
};

var sayHello = function (req,res,next) {
    res.send('Hello '+req.param('name'));
};
var app = express();
app.get('/',hasName,sayHello);
app.listen(3000);
console.log('Server is running at http://localhost:3000/');

上圖例子基本功能就是:當(dāng)訪問服務(wù)沒有加名字參數(shù)時顯示 what is your name, 有名字的時候顯示 hello XXX。用到了兩個middleware,hasName( )檢查是否有name參數(shù),如果有就執(zhí)行next,如果沒有,就結(jié)束處理返回相應(yīng)。sayHello( )輸出你好信息。通過app.get( )將兩個middleware加入row,Express會按照順序注冊middleware。

在項(xiàng)目中加入路由文件

在app/routes目錄,創(chuàng)建index.server.routes.js:

module.exports = function (app) {
    var index = require('../controllers/index.server.controller');
    app.get('/',index.render);
};

上代碼,使用CommonJS模塊模式exports。將render( )方法當(dāng)做一個middleware導(dǎo)入了根路徑的GET請求。
接下來,需要創(chuàng)建一個Express app實(shí)例,然后啟動應(yīng)用。在config創(chuàng)建express.js文件:

var express = require('express');

module.exports = function () {
    var app = express();
    require('../app/routes/index.server.routes')(app);
    return(app);
};

上述代碼:

  • 創(chuàng)建了Express應(yīng)用實(shí)例 app
  • require路由文件并把實(shí)例app傳入
  • 路由文件使用app實(shí)例創(chuàng)建了路由配置并調(diào)用了controller的render( )方法
  • module function 通過返回app實(shí)例結(jié)束

express.js文件是Express應(yīng)用的配合文件,所有Express相關(guān)的配置都寫在這里

最后,在根目錄創(chuàng)建server.js文件:

var express = require('./config/express');

var app = express();
app.listen(3000);
module.exports = app;
console.log('Server is running at http://localhost:3000/');

然后,命令行cd 到項(xiàng)目根目錄,npm安裝依賴包:
$ npm install
最后運(yùn)行應(yīng)用:
$node server

瀏覽器中:http://localhost:3000/ 可以看到Hello World,一個Express應(yīng)用就搭建完成!

配置Express應(yīng)用

雖然Express提供了一個方便的方式來配置并且還可以使用key/value的形式,但是Express 還有一種更強(qiáng)大的方式來使配置文件基于運(yùn)行環(huán)境來適配。例如,開發(fā)環(huán)境中,你希望Express打出log,而發(fā)布環(huán)境中則不需要。為了達(dá)到這樣的效果,需要配置process.env變量,這是一個全局變量。NODE_ENV是最常用的環(huán)境變量。下文通過例子來理解這一點(diǎn)。

首先來增加一些依賴包,編輯package.json:

{
     "name": "MEAN",
     "version": "0.0.3",
     "dependencies": {
       "express": "~4.8.8",
       "morgan": "~1.3.0",
       "compression": "~1.0.11",
       "body-parser": "~1.8.0",
       "method-override": "~2.2.0"
  } 
}
  • morgan:簡單地logger
  • compression:是一個response壓縮
  • body-parser:處理request數(shù)據(jù)
  • method-override:提供DELETE、PUT等HTTP請求方式的處理

若要使用以上module,需要在config/express.js文件做以下修改

var express = require('express'),
    morgan = require('morgan'),
    bodyParser = require('body-parser'),
    methodOverride = require('method-override'),
    compress = require('compression');
module.exports = function () {
    var app = express();

    if (process.env.NODE_ENV === 'development'){
        app.use(morgan('dev'));
    }else if(process.env.NODE_ENV == 'production'){
        app.use(compress());
    }

    app.use(bodyParser.urlencoded({
        extended: true
    }));

    app.use(bodyParser.json());
    app.use(methodOverride());
    require('../app/routes/index.server.routes')(app);
    return(app);
};

然后修改server.js:

process.env.NODE_ENV = process.env.NODE_ENV || 'development'

var express = require('./config/express');

var app = express();
app.listen(3000);
module.exports = app;

console.log('Server is running at http://localhost:3000/');

最后命令行cd 到工程根目錄:
$ npm install
$ npm server
去瀏覽器試一下吧!

MEAN Web Development
It is recommended that you set the NODE_ENV environment variable inyour operating system prior to running your application.
In a Windows environment, this can be done by executing the followingcommand in your command prompt:
set NODE_ENV=development
While in a Unix-based environment, you should simply use the followingexport command:
$ export NODE_ENV=development

環(huán)境配置文件

開發(fā)中,會經(jīng)常在不同的環(huán)境使用不同的第三方module。例如,當(dāng)應(yīng)用連接到MongoDB時,開發(fā)/生產(chǎn)環(huán)境可能會使用不同的連接字符。以現(xiàn)有的架構(gòu)來區(qū)分這些環(huán)境可能會在代碼中加入很多的if/else,把代碼搞得很難維護(hù)。為了避免這樣的事情發(fā)生,開發(fā)者可以通過建立多個配置文件來對應(yīng)多種環(huán)境。
在config/env目錄建立文件development.js:

module.exports = {
    //Development configuration  options
};

修改配置加載,在config目錄新建config.js文件:

module.exports = require('./env/'+process.env.NODE_ENV+'.js');

上述代碼通過在不同的環(huán)境生成不同的路徑來加載不同的配置文件

渲染界面

web框架的最基本的功能就是渲染界面(view),最基本的認(rèn)知就是個模板引擎提供數(shù)據(jù),然后渲染成HTML。在MVC模式下,控制器(Controller)使用模型(Model)把數(shù)據(jù)(Data)分配,通過界面模板渲染HTML。如下圖所示

MVC

Express允許使用很多的Node.js模板引擎來實(shí)現(xiàn)這一功能。本文使用EJS引擎為例。

Express有兩個方法來渲染界面:

  • app.render( ) :渲染界面將HTML發(fā)送給回調(diào)函數(shù)
  • res.render( ) :本地渲染界面,發(fā)送HTML作為請求的響應(yīng)。
    上述方法res.render( ) 是比較常用的,應(yīng)為HTTP請求是常用的交互形式;當(dāng)你需要發(fā)送HTTP的email,或許會用到app.render( )方法。

在使用渲染方法之前,首先需要配置界面系統(tǒng)(view systerm),在package.json中加入以下依賴包:
"ejs":"~1.0.0"
然后使用 $ npm update加入ejs模塊
安裝ejs后,在config/express.js文件中增加ejs的配置:

var express = require('express'),
    morgan = require('morgan'),
    bodyParser = require('body-parser'),
    methodOverride = require('method-override'),
    compress = require('compression');
module.exports = function () {
    var app = express();

    if (process.env.NODE_ENV === 'development'){
        app.use(morgan('dev'));
    }else if(process.env.NODE_ENV == 'production'){
        app.use(compress());
    }

    app.use(bodyParser.urlencoded({
        extended: true
    }));

    app.use(bodyParser.json());
    app.use(methodOverride());

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

    require('../app/routes/index.server.routes')(app);
    return(app);
};

上述代碼使用app.set( )配置了Express application的views文件夾和模板引擎,接下來就可以創(chuàng)建第一個view了!

渲染 EJS view

EJS界面基本是由HTML和EJS 標(biāo)簽組成的。EJS模板將會調(diào)整app/views文件夾,會有.ejs擴(kuò)展。當(dāng)你使用res.render( )方法時,EJS引擎將會在views文件夾尋找模板,如果找到了就渲染HTML輸出。

創(chuàng)建第一個EJS view

在app/views目錄,新建index.ejs:

<! DOCTYPE html>
<html>

    <head>
        <title><%= title %>></title>    
    </head>
    <body>
        <h1><%= title%></h1>
    </body>
</html>

上述,<%= %>標(biāo)簽是告訴EJS模板引擎來渲染模板變量,上例子中是 title 變量。接下來需要配置控制器自動渲染模板然后輸出HTML響應(yīng)。在app/controllers/index.server.controller.js文件中做以下修改:

exports.render = function (req,res) {
    res.render('index',{
        title:'Hello EJS'
    })
};

上述,注意res.render( )的使用方法,第一個參數(shù)要寫EJS模板的文件名,第二個參數(shù)寫一個模板要用的實(shí)例。
$ node server
運(yùn)行服務(wù),試一下吧!

EJS維護(hù)起來比較簡單,但是比較復(fù)雜的渲染就要用到后面要介紹的AngularJS了。

保存靜態(tài)文件

Express提供了express.static( )middleware來處理靜態(tài)文件。在config/express.js中加入如下改動

var express = require('express'),
    morgan = require('morgan'),
    bodyParser = require('body-parser'),
    methodOverride = require('method-override'),
    compress = require('compression');
module.exports = function () {
    var app = express();

    if (process.env.NODE_ENV === 'development'){
        app.use(morgan('dev'));
    }else if(process.env.NODE_ENV == 'production'){
        app.use(compress());
    }

    app.use(bodyParser.urlencoded({
        extended: true
    }));

    app.use(bodyParser.json());
    app.use(methodOverride());

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

    require('../app/routes/index.server.routes')(app);
    //注意代碼順序,在路由之后
    app.use(express.static('./public'));
    
    return(app);
};

上述, app.use(express.static('./public')) 告訴了Express靜態(tài)文件存放的路徑,注意要把這段代碼放到路由之后,因?yàn)榉旁诼酚芍暗脑扙xpress會先從HTTP請求path中尋找靜態(tài)文件,這樣會讓性能變慢。
下面,加入logo的靜態(tài)變量在public/img目錄,然后對app/views/index.ejs作如下修改:

<! DOCTYPE html>
<html>
    <head>
        <title> <%= title %> </title>
    </head>
    <body>
    ![](img/logo.png)
        <h1><%= title%></h1>
    </body>
</html>

處理Session

大多的Web應(yīng)用都會用到Session。想要增加這個功能,需要npm導(dǎo)入express-session包。更改package.json:

{
  "name":"MEAN",
  "version": "0.0.2",
  "dependencies": {
    "express": "~4.8.8",
    "morgan": "~1.3.0",
    "compression": "~1.0.11",
    "body-parser": "~1.8.0",
    "method-override": "~2.2.0",
    "express-session":"~1.7.6",
    "ejs" : "~1.0.0"
  }
}

然后:
$ npm undate

express-session模塊有:

  • cookie-stored
  • signed identifier
    要標(biāo)記session identifier 需要用到secret string來預(yù)防惡意session干預(yù)。為了安全,cookie secret在不同的環(huán)境需要不同,需要用到配置文件。在config/env/development.js做出修改:
module.exports = {
    //Development configuration  options
    sessionSecret:'developmentSessionSecret'
};

待續(xù)...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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