Express使用手记:核心入门

glyn7338 8年前
   <h2><strong>入门简介</strong></h2>    <h3>Express是基于nodejs的web开发框架。优点是易上手、高性能、扩展性强。</h3>    <ul>     <li> <h3><strong>易上手</strong> :nodejs最初就是为了开发高性能web服务器而被设计出来的,然而相对底层的API会让不少新手望而却步。express对web开发相关的模块进行了适度的封装,屏蔽了大量复杂繁琐的技术细节,让开发者只需要专注于业务逻辑的开发,极大的降低了入门和学习的成本。</h3> </li>     <li> <h3><strong>高性能</strong> :express仅在web应用相关的nodejs模块上进行了适度的封装和扩展,较大程度避免了过度封装导致的性能损耗。</h3> </li>     <li> <h3><strong>扩展性强</strong> :基于中间件的开发模式,使得express应用的扩展、模块拆分非常简单,既灵活,扩展性又强。</h3> </li>    </ul>    <h2><strong>环境准备</strong></h2>    <p>首先,需要安装nodejs,这一步请自行解决。接着,安装express的脚手架工具 express-generator ,这对于我们学习express很有帮助。</p>    <pre>  <code class="language-javascript">npm install -g express-generator</code></pre>    <h2><strong>第一个demo</strong></h2>    <p>利用之前安装的脚手架工具,初始化我们的demo项目。</p>    <pre>  <code class="language-javascript">/tmp mkdir express-demo    /tmp cd express-demo     express-demo express       create : .     create : ./package.json     create : ./app.js     create : ./public     create : ./public/javascripts     create : ./public/images     create : ./public/stylesheets     create : ./public/stylesheets/style.css     create : ./routes     create : ./routes/index.js     create : ./routes/users.js     create : ./views     create : ./views/index.jade     create : ./views/layout.jade     create : ./views/error.jade     create : ./bin     create : ./bin/www       install dependencies:       $ cd . && npm install       run the app:       $ DEBUG=express-demo:* npm start</code></pre>    <p>按照指引,安装依赖。并启动服务</p>    <pre>  <code class="language-javascript">npm install</code></pre>    <p>然后,启动服务器。</p>    <pre>  <code class="language-javascript">express-demo  npm start    > ex1@0.0.0 start /private/tmp/ex1  > node ./bin/www</code></pre>    <p>访问浏览器,迈出成功的第一步。</p>    <p><img src="https://simg.open-open.com/show/d1b5a3d34f5f2609044877a4d6e63528.jpg"></p>    <h2><strong>目录结构介绍</strong></h2>    <p>看下demo应用的目录结构。大部分时候,我们的应用目录结构跟这个保持一致就可以了。也可以根据需要自行调整,express并没有对目录结构进行限制。</p>    <p>从目录结构可以大致看出,express应用的核心概念主要包括: 路由 、 中间件 、 模板引擎 。</p>    <pre>  <code class="language-javascript">express-demo tree -L 1  .  ├── app.js # 应用的主入口  ├── bin  # 启动脚本  ├── node_modules # 依赖的模块  ├── package.json # node模块的配置文件  ├── public # 静态资源,如css、js等存放的目录  ├── routes # 路由规则存放的目录  └── views # 模板文件存放的目录    5 directories, 2 files</code></pre>    <h2><strong>核心概念简介</strong></h2>    <p>上面提到,express主要包含三个核心概念:路由、中间件、模板引擎。</p>    <p>注意,笔者这里用的是 核心概念 这样的字眼,而不是 核心模块 ,为什么呢?这是因为,虽然express的中间件有它的定义规范,但是express的内核源码中,其实是没有所谓的 <em>中间件</em> 这样的模块的。</p>    <p>言归正传,三者简要的来说就是。</p>    <ul>     <li>中间件 :可以毫不夸张的说,在express应用中,一切皆中间件。各种应用逻辑,如cookie解析、会话处理、日志记录、权限校验等,都是通过中间件来完成的。</li>     <li>路由 :地球人都知道,负责寻址的。比如用户发送了个http请求,该定位到哪个资源,就是路由说了算。</li>     <li>模板引擎 :负责视图动态渲染。下面会介绍相关配置,以及如何开发自己的模板引擎。</li>    </ul>    <h2><strong>核心概念:路由</strong></h2>    <h3><strong>路由分类</strong></h3>    <p>粗略来说,express主要支持四种类型的路由,下面会分别举例进行说明</p>    <ol>     <li>字符串类型</li>     <li>字符串模式类型</li>     <li>正则表达式类型</li>     <li>参数类型</li>    </ol>    <p>分别举例如下,细节可参考 <a href="/misc/goto?guid=4959713798799264749" rel="nofollow,noindex">官方文档</a> 。</p>    <pre>  <code class="language-javascript">var express = require('express');  var app = express();    // 路由:字符串类型  app.get('/book', function(req, res, next){      res.send('book');  });    // 路由:字符串模式  app.get('/user/*man', function(req, res, next){      res.send('user');  // 比如: /user/man, /user/woman  });    // 路由:正则表达式  app.get(/animals?$/, function(req, res, next){      res.send('animal');  // 比如: /animal, /animals  });    // 路由:命名参数  app.get('/employee/:uid/:age', function(req, res, next){      res.json(req.params);  // 比如:/111/30,返回 {"uid": 111, "age": 30}  });    app.listen(3000);</code></pre>    <h3><strong>路由拆分</strong></h3>    <p>当你用的应用越来越复杂,不可避免的,路由规则也会越来越复杂。这个时候,对路由进行拆分是个不错的选择。</p>    <p>我们分别看下两段代码,路由拆分的好处就直观的体现出来了。</p>    <p>路由拆分前</p>    <pre>  <code class="language-javascript">var express = require('express');  var app = express();    app.get('/user/list', function(req, res, next){      res.send('/list');  });    app.get('/user/detail', function(req, res, next){      res.send('/detail');  });    app.listen(3000);</code></pre>    <p>这样的代码会带来什么问题呢?无论是新增还是修改路由,都要带着 /user 前缀,这对于代码的可维护性来说是大忌。这对小应用来说问题不大,但应用复杂度一上来就会是个噩梦。</p>    <p><strong>路由拆分后</strong></p>    <p>可以看到,通过 express.Router() 进行了路由拆分,新增、修改路由都变得极为便利。</p>    <pre>  <code class="language-javascript">var express = require('express');  var app = express();    var user = express.Router();    user.get('/list', function(req, res, next){      res.send('/list');  });    user.get('/detail', function(req, res, next){      res.send('/detail');  });    app.use('/user', user); // mini app,通常做应用拆分    app.listen(3000);</code></pre>    <h2><strong>核心概念:中间件</strong></h2>    <p>一般学习js的时候,我们都会听到一句话:一切皆对象。而在学习express的过程中,很深的一个感受就是:一切皆中间件。比如常见的请求参数解析、cookie解析、gzip等,都可以通过中间件来完成。</p>    <h3><strong>工作机制</strong></h3>    <p>贴上官网的 <a href="/misc/goto?guid=4959713798897493931" rel="nofollow,noindex">一张图</a> 镇楼,图中所示就是传说中的中间件了。</p>    <p><img src="https://simg.open-open.com/show/c0bf0be7aaeb4347c152147b687637f4.jpg"></p>    <p>首先,我们自己编写一个极简的中间件。虽然没什么实用价值,但中间件就长这样子。</p>    <ul>     <li>参数 :三个参数,熟悉 http.createServer() 的同学应该比较眼熟,其实就是req(客户端请求实例)、res(服务端返回实例),只不过进行了扩展,添加了一些使用方法。</li>     <li>next :回调方法,当next()被调用时,就进入下一个中间件。</li>    </ul>    <pre>  <code class="language-javascript">function logger(req, res, next){      console.log('here comes request');      next();  }</code></pre>    <p>来看下实际例子:</p>    <pre>  <code class="language-javascript">var express = require('express');  var app = express();    app.use(function(req, res, next) {      console.log('1');      next();  });    app.use(function(req, res, next) {      console.log('2');      next();  });    app.use(function(req, res, next) {      console.log('3');      res.send('hello');  });    app.listen(3000);</code></pre>    <p>请求 <a href="/misc/goto?guid=4959713798976255515" rel="nofollow,noindex">http://127.0.0.1:3000,看下控制台输出,以及浏览器返回内容。</a></p>    <pre>  <code class="language-javascript">middleware git:(master)  node chains.js   1  2  3</code></pre>    <p><img src="https://simg.open-open.com/show/3315d6bb8c9ee56dfe0e166a27524e0b.jpg"></p>    <h3><strong>应用级中间件 vs 路由级中间件</strong></h3>    <p>根据作用范围,中间件分为两大类:</p>    <ul>     <li>应用级中间件</li>     <li>路由级中间件。</li>    </ul>    <p>两者的区别不容易说清楚,因为从本质来讲,两类中间件是完全等同的,只是使用场景不同。同一个中间件,既可以是应用级中间件、也可以是路由级中间件。</p>    <p>直接上代码可能更直观。参考下面代码,可以简单粗暴的认为:</p>    <ul>     <li>应用级中间件: app.use() 、 app.METHODS() 接口中使用的中间件。</li>     <li>路由级中间件: router.use() 、 router.METHODS() 接口中使用的中间件。</li>    </ul>    <pre>  <code class="language-javascript">var express = require('express');  var app = express();  var user = express.Router();    // 应用级  app.use(function(req, res, next){      console.log('收到请求,地址为:' + req.url);      next();  });    // 应用级  app.get('/profile', function(req, res, next){      res.send('profile');  });    // 路由级  user.use('/list', function(req, res, next){      res.send('/user/list');  });    // 路由级  user.get('/detail', function(req, res, next){      res.send('/user/detail');  });    app.use('/user', user);    app.listen(3000);</code></pre>    <h3><strong>开发中间件</strong></h3>    <p>上面也提到了,中间件的开发是是分分钟的事情,不赘述。</p>    <pre>  <code class="language-javascript">function logger(req, res, next){      doSomeBusinessLogic(); // 业务逻辑处理,比如权限校验、数据库操作、设置cookie等      next();  // 如果需要进入下一个中间件进行处理,则调用next();  }</code></pre>    <h3><strong>常用中间件</strong></h3>    <p>包括但不限于如下。更多常用中间件,可以点击 <a href="/misc/goto?guid=4959713799059747076" rel="nofollow,noindex">这里</a></p>    <ul>     <li>body-parser</li>     <li>compression</li>     <li>serve-static</li>     <li>session</li>     <li>cookie-parser</li>     <li>morgan</li>    </ul>    <h2><strong>核心概念:模板引擎</strong></h2>    <p>模板引擎大家不陌生了,关于express模板引擎的介绍可以参考 <a href="/misc/goto?guid=4959713799145606911" rel="nofollow,noindex">官方文档</a> 。</p>    <p>下面主要讲下使用配置、选型等方面的内容。</p>    <h3><strong>可选的模版引擎</strong></h3>    <p>包括但不限于如下模板引擎</p>    <ul>     <li>jade</li>     <li>ejs</li>     <li>dust.js</li>     <li>dot</li>     <li>mustache</li>     <li>handlerbar</li>     <li><a href="/misc/goto?guid=4959713799220882159" rel="nofollow,noindex">nunjunks</a></li>    </ul>    <h3><strong>配置说明</strong></h3>    <p>先看代码。</p>    <pre>  <code class="language-javascript">// view engine setup  app.set('views', path.join(__dirname, 'views'));  app.set('view engine', 'jade');</code></pre>    <p>有两个关于模版引擎的配置:</p>    <ol>     <li>views :模版文件放在哪里,默认是在项目根目录下。举个例子: app.set('views', './views')</li>     <li>view engine :使用什么模版引擎,举例: app.set('view engine', 'jade')</li>    </ol>    <p>可以看到,默认是用 jade 做模版的。如果不想用 jade 怎么办呢?下面会提供一些模板引擎选择的思路。</p>    <h3><strong>选择标准</strong></h3>    <p>需要考虑两点:实际业务需求、个人偏好。</p>    <p>首先考虑业务需求,需要支持以下几点特性。</p>    <ul>     <li>支持模版继承(extend)</li>     <li>支持模版扩展(block)</li>     <li>支持模版组合(include)</li>     <li>支持预编译</li>    </ul>    <p>对比了下, jade 、 nunjunks 都满足要求。个人更习惯 nunjunks 的风格,于是敲定。那么,怎么样使用呢?</p>    <h3><strong>支持nunjucks</strong></h3>    <p>首先,安装依赖</p>    <pre>  <code class="language-javascript">npm install --save nunjucks</code></pre>    <p>然后,添加如下配置</p>    <pre>  <code class="language-javascript">var nunjucks = require('nunjucks');    nunjucks.configure('views', {      autoescape: true,      express: app  });    app.set('view engine', 'html');</code></pre>    <p>看下 views/layout.html</p>    <pre>  <code class="language-javascript"><!DOCTYPE html>  <html>  <head>      <title>          {% block title %}              layout title          {% endblock %}      </title>  </head>  <body>  <h1>      {% block appTitle %}          layout app title      {% endblock %}  </h1>  <p>正文</p>    </body>  </html></code></pre>    <p>看下 views/index.html</p>    <pre>  <code class="language-javascript">{% extends "layout.html" %}  {% block title %}首页{% endblock %}  {% block appTitle %}首页{% endblock %}</code></pre>    <h3><strong>开发模板引擎</strong></h3>    <p>通过 app.engine(engineExt, engineFunc) 来注册模板引擎。其中</p>    <ul>     <li>engineExt:模板文件后缀名。比如 jade 。</li>     <li>engineFunc:模板引擎核心逻辑的定义,一个带三个参数的函数(如下)</li>    </ul>    <pre>  <code class="language-javascript">// filepath: 模板文件的路径  // options:渲染模板所用的参数  // callback:渲染完成回调  app.engine(engineExt, function(filepath, options, callback){        // 参数一:渲染过程的错误,如成功,则为null      // 参数二:渲染出来的字符串      return callback(null, 'Hello World');  });</code></pre>    <p>比如下面例子,注册模板引擎 + 修改配置一起,于是就可以愉快的使用后缀为 tmpl 的模板引擎了。</p>    <pre>  <code class="language-javascript">app.engine('tmpl', function(filepath, options, callback){        // 参数一:渲染过程的错误,如成功,则为null      // 参数二:渲染出来的字符串      return callback(null, 'Hello World');  });  app.set('views', './views');  app.set('view engine', 'tmpl');</code></pre>    <h3><strong>相关链接</strong></h3>    <p>模板引擎对比: <a href="/misc/goto?guid=4959713799308466881" rel="nofollow,noindex">点击这里</a></p>    <p>express模版引擎介绍: <a href="/misc/goto?guid=4959713799145606911" rel="nofollow,noindex">点击这里</a></p>    <p>开发模版引擎: <a href="/misc/goto?guid=4959713799397705895" rel="nofollow,noindex">点击这里</a></p>    <h2><strong>更多内容</strong></h2>    <p>前面讲了一些express的入门基础,感兴趣的同学可以查看官方文档。篇幅所限,有些内容在后续文章展开,比如下面列出来的内容等。</p>    <ul>     <li>进程管理</li>     <li>会话管理</li>     <li>日志管理</li>     <li>性能优化</li>     <li>调试</li>     <li>错误处理</li>     <li>负载均衡</li>     <li>数据库支持</li>     <li>HTTPS支持</li>     <li>业务实践</li>     <li>。。。</li>    </ul>    <p> </p>    <p>来自:http://imweb.io/topic/57c8cb417f226f687b365634</p>    <p> </p>