学习Node.js全栈框架MEAN-04-Express

Express是Node社区里的超级明星,他的作者TJ Holowaychuk也因此成为了社区里大红大紫的开发者。Express框架是基于Connect的小型Web服务常见功能模块支持的框架。之前,我们只是在sever.js一个文件中开发,但Express会引导我们去建立组织更合理更庞大的项目。

安装Express

还是使用npm:
npm install express
这种导入包的方法存在当项目使用了很多包后管理起来很麻烦的缺点,所以介绍一种新的方法:package.json配置文件

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

配置文件有三个key,其中dependencies代表了以来的包。然后在所在目录执行:
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/')
//导出
module.exports = app;

上代码就是一个简单地Express工程,有以下几点留意:

  • app.use( ),沿用了Connect的方式,注册middleware
  • res.send( )方法集成了设置Content-Type,和res.end( )方法
  • module.exports方法导出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

这三个实例是Express里最常用的。

Application

Application 实例有以下常用方法,帮助开发者配置web服务:

  • app.set(name ,value):设置环境变量
  • app.get(name):获取环境变量
  • app.engine(ext, callback):设置模板引擎来渲染文件。比如使用EJS模板引擎来模板化HTML文件:app.('html',require('ejs').renderFile)
  • app.use([path], callback):创建 Express middleware来处理HTTP请求。
  • app.VERB(path, [callback...], callback):定义一个或多个middleware,和例如GET这样的verb相关
  • app.route(path).VERB([callback...], callback): 定义一个或多个middleware,以及他们对应的verb和路径
  • app.param([name], callback):给一个请求所对应的方法绑定路由参数。

Request

处理http请求的实例,以下是常用的变量/方法

  • req.query:包含了query-string变量
  • req.params:包含了路径变量
  • req.body:检索请求body,这个变量也包含该在bodyParser( )中间件中
  • req.param(name):检索一个请求参数值
  • req.path,req.host,req.ip: 请求的路径/主机/IP
  • req.cookies: 和中间件cookieParser( )一起使用,来检索用户发出的cookie

Response

处理HTTP响应,常用方法:

  • res.status(code):设置响应HTTP状态码
  • res.set(field [value]):设置响应头 HTTP header
  • res.cookie (name, value, [option] ):设置响应cookie,option参数用来配置cookie,例如maxAge
  • res.redirect([status], url):再次定向请求一个新的URL,同时可以传递一个HTTP状态码,如果不设置,默认302
  • res.send([body|status], [body]):用于non-streaming 响应。这个方法同时做了诸如设置Content-Type、Content-Length 头和缓存头的操作。
  • res.json([status|body], [body]):和res.send( )一样,发送object或者array。多数时候用作语法糖,但是有些时候比如强制发送一个空JSON响应。
  • res.render(view, [locals], callback):渲染一个view或发出一个HTML响应

外部middleware

  • Morgan:HTTP请求logger
  • body-parser:处理请求body,支持多种请求类型
  • method-override:支持HTTP verb例如PUT DELETE
  • Compression:gzip/deflate压缩相应数据
  • express.static:处理静态文件
  • cookie-parser:包含于res.cookies实例
  • Session:持久化session

MVC

Express 是patten agnostic(模式不可知论者??不懂);也就意味着,Express不支持任何预定义语法。在Express中实现MVC可以通过工程文件夹结构来实现。

项目文件结构

水平布局

水平布局是从功能这一维度维度对文件进行分组整理。文件会依据其在项目中的功能进行分组,一般来说所有的文件都放到一个main application文件夹,然后分为MVC结构(controllers文件夹放所有的控制器,model文件夹放所有的数据model...)例如下图所示:

文件的结构如下:

  • app文件夹是存放Express应用逻辑的文件夹,依据MVC的模式又分为以下子文件夹
    • Controllers文件夹存放应用的控制器
    • models存放model
    • routes存放路由中间件middleware
    • views存放界面view
  • config文件夹存放Express应用的配置文件,当需要加入更多的module的时候,每一个model的配置文件也放到这里。目前这个文件夹有以下几个子目录:
    • env存放环境配置文件
    • config.js存放整个应用的配置
    • express.js存放Express的初始化配置
  • public 文件夹存放静态客户端文件,按照MVC的方式又有以下子目录:
    • config目录存放AngularJS应用的配置文件
    • Controllers存放控制器
    • css放css(有点废话的感觉O(∩_∩)O~)
    • directives 存放AngularJS的指示
    • filters存放AngularJS的过滤器
    • img放图片
    • views 放AngularJS的view
    • Application.js 用来初始化AngularJS应用
  • package.json 用来管理项目的依赖包
  • server.js是Node.js的main文件,用来加载express.js文件,是Express应用启动的文件

水平项目目录结构很适合比较小的项目,各个模块用起来比较方便,也可以清晰的看到每个功能模块的结构。但是当项目复杂的时候,这种目录结构就会显得非常臃肿,hold不住众多的功能模块。大项目还是用垂直文件结构比较合适。垂直结构就不详细介绍了,看一下下面这张图应该也就明白了:

水平结构

接下来,使用水平结构创建了一个小项目,目录如下,在根目录创建并编辑package.json:

Project

然后,在app/controller文件夹,创建一个index.server.controller.js文件,敲入以下代码:

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

以上代码,是一个Express controller,使用CommonJS 模块定义了一个function叫做render( )。当定义了一个controller后,需要在Express routing(路由)使用它。

处理请求路由

一般使用Express 的 app.route(path).VERB(callback)或者app.VERB(path,callback)方法来处理路由。方法中的VERB在实际使用的收替换成小写的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的方式来实现处理多种方式的请求,例如:

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可以按照顺序被依次调用。在执行请求前做验证。例子:

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/');

上图例子基本功能就是:当访问服务没有加名字参数时显示 what is your name, 有名字的时候显示 hello XXX。用到了两个middleware,hasName( )检查是否有name参数,如果有就执行next,如果没有,就结束处理返回相应。sayHello( )输出你好信息。通过app.get( )将两个middleware加入row,Express会按照顺序注册middleware。

在项目中加入路由文件

在app/routes目录,创建index.server.routes.js:

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

上代码,使用CommonJS模块模式exports。将render( )方法当做一个middleware导入了根路径的GET请求。
接下来,需要创建一个Express app实例,然后启动应用。在config创建express.js文件:

var express = require('express');

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

上述代码:

  • 创建了Express应用实例 app
  • require路由文件并把实例app传入
  • 路由文件使用app实例创建了路由配置并调用了controller的render( )方法
  • module function 通过返回app实例结束

express.js文件是Express应用的配合文件,所有Express相关的配置都写在这里

最后,在根目录创建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 到项目根目录,npm安装依赖包:
$ npm install
最后运行应用:
$node server

浏览器中:http://localhost:3000/ 可以看到Hello World,一个Express应用就搭建完成!

配置Express应用

虽然Express提供了一个方便的方式来配置并且还可以使用key/value的形式,但是Express 还有一种更强大的方式来使配置文件基于运行环境来适配。例如,开发环境中,你希望Express打出log,而发布环境中则不需要。为了达到这样的效果,需要配置process.env变量,这是一个全局变量。NODE_ENV是最常用的环境变量。下文通过例子来理解这一点。

首先来增加一些依赖包,编辑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数据
  • 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

环境配置文件

开发中,会经常在不同的环境使用不同的第三方module。例如,当应用连接到MongoDB时,开发/生产环境可能会使用不同的连接字符。以现有的架构来区分这些环境可能会在代码中加入很多的if/else,把代码搞得很难维护。为了避免这样的事情发生,开发者可以通过建立多个配置文件来对应多种环境。
在config/env目录建立文件development.js:

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

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

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

上述代码通过在不同的环境生成不同的路径来加载不同的配置文件

渲染界面

web框架的最基本的功能就是渲染界面(view),最基本的认知就是个模板引擎提供数据,然后渲染成HTML。在MVC模式下,控制器(Controller)使用模型(Model)把数据(Data)分配,通过界面模板渲染HTML。如下图所示

MVC

Express允许使用很多的Node.js模板引擎来实现这一功能。本文使用EJS引擎为例。

Express有两个方法来渲染界面:

  • app.render( ) :渲染界面将HTML发送给回调函数
  • res.render( ) :本地渲染界面,发送HTML作为请求的响应。
    上述方法res.render( ) 是比较常用的,应为HTTP请求是常用的交互形式;当你需要发送HTTP的email,或许会用到app.render( )方法。

在使用渲染方法之前,首先需要配置界面系统(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文件夹和模板引擎,接下来就可以创建第一个view了!

渲染 EJS view

EJS界面基本是由HTML和EJS 标签组成的。EJS模板将会调整app/views文件夹,会有.ejs扩展。当你使用res.render( )方法时,EJS引擎将会在views文件夹寻找模板,如果找到了就渲染HTML输出。

创建第一个EJS view

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

<! DOCTYPE html>
<html>

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

上述,<%= %>标签是告诉EJS模板引擎来渲染模板变量,上例子中是 title 变量。接下来需要配置控制器自动渲染模板然后输出HTML响应。在app/controllers/index.server.controller.js文件中做以下修改:

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

上述,注意res.render( )的使用方法,第一个参数要写EJS模板的文件名,第二个参数写一个模板要用的实例。
$ node server
运行服务,试一下吧!

EJS维护起来比较简单,但是比较复杂的渲染就要用到后面要介绍的AngularJS了。

保存静态文件

Express提供了express.static( )middleware来处理静态文件。在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静态文件存放的路径,注意要把这段代码放到路由之后,因为放在路由之前的话Express会先从HTTP请求path中寻找静态文件,这样会让性能变慢。
下面,加入logo的静态变量在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应用都会用到Session。想要增加这个功能,需要npm导入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
    要标记session identifier 需要用到secret string来预防恶意session干预。为了安全,cookie secret在不同的环境需要不同,需要用到配置文件。在config/env/development.js做出修改:
module.exports = {
    //Development configuration  options
    sessionSecret:'developmentSessionSecret'
};

待续...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容