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)創(chuàng)建了一個小項(xiàng)目,目錄如下,在根目錄創(chuàng)建并編輯package.json:
然后,在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。如下圖所示
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>

<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ù)...