1. 如何引入express?
cnpm install express --save
其中--save可以保存到依赖项中。
接着 var express = require("express"); 即可。这里express只是一个模块。
注意: 有时候我们会看到有人使用 npm i express --save 的方式来安装,这样也是可行的,因为 npm i 就是 npm install 的简写形式。
2. 什么是并且如何使用express-generator?
这是一个生成express的生成器,通过它,我们可以快速构建一个express架构,而无需自己繁琐的一项一项构建。
cnpn install express-generator -g (管理员方式打开命令窗口)
注意:如果是在当前目录安装就不需要使用管理员方式,但是如果全局安装,就一定要使用管理员方式。因为这里创建了一个生成器,所以就像构造函数一样可以去创建实例,那么express在命令行中就是相当于一个可执行文件, 如果不是全局安装,就必须要在express-generator的文件目录下才能执行 ,非常繁琐,但是如果全局安装,我们在任何目录下都可以执行该命令。 如:
express myapp
就可以创建一个express架构。
这里默认使用的模板引擎是jade,如果希望使用ejs模板引擎,可以是 npm -e myapp, 其中的-e就代表使用ejs模板引擎。
如下:
注意:即通过express-generator我们再执行 express <项目名称> 可以快速构建一个架构。 其中myapp就是这样一个文件,包含了package.json(此文件中的依赖项中包含了各种express所需的包),app.js(即入口文件)、pulic即其下面的一些文件夹用于存放相应的文件 ,以及路由等等。 如果不使用 express-generator ,我们就得自己一个一个的创建,这是相当麻烦的 。 另外,如果有不符合我们项目的地方,我们直接修改即可。 创建的同时提示首先 cd myapp (即进入myapp文件夹)然后 npm install(安装package.json中的依赖项),完成依赖项的安装(即package.json文件中的依赖项安装)。我们可以看到package.json中的依赖项如下:
{ "name": "myapp", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "body-parser": "~1.17.1", "cookie-parser": "~1.4.3", "debug": "~2.6.3", "express": "~4.15.2", "jade": "~1.11.0", "morgan": "~1.8.1", "serve-favicon": "~2.4.2" } }
其中body-parser在post请求时必须使用;cookie-parser在处理cookie时必须使用; debug模块用于调试,类似于console.log; express就更不用说了;jade是模板引擎模块,这对于服务器端语言还是非常重要的。morgan是一个日志模块,用于在后台打印出req请求等等,方便我们在后台查看,这与debug模块非常相似,但是debug模块是用于取代console.log的,而morgan主要是用于查看请求的。serve-favicon不太懂,后面学习。
cd myapp
npm install
注意: 在安装过程中,会提示jade已经更名为pug,也就是说两者是一回事。
在安装完了所有的依赖项之后,我们就可以使用下面的命令来启动这个应用了:
set DEBUG=myapp:* & npm start
这里 set DEBUG=myapp:* & npm start 就可以来启动了,而前者是说启动debug模块,打印一些debug日志方便我们管理后台。 注意:和*之间有空格和没有空格是不同的。 这里使用的没有空格。
另外,如果不希望使用debug模块,像下面这样就可以启动了。
npm start
在浏览器中进入localhost:3000, 如下所示:
通过 Express 应用生成器创建的应用一般都有如下目录结构:
(补充):其中routes文件是怎么是使用的呢?
其实不用routes文件当然也是可以的,但是在实际开发中, 路由文件动辄成百上千,如果全部放在 app.js 中, 不难想象app.js将会多么臃肿, 所以我们需要对于不同路径的路由放在不同的文件下。
对于 express-generator 生成的模板, app.js 的内容如下:
var express = require('express'); var app = express(); var indexRouter = require('./routes/index'); var userRouter = require('./routes/users'); app.use('/', indexRouter); app.use('/users', userRouter); app.listen(3000);
然后进入routes下的index我们可以看到:
var express = require('express'); var router = express.Router(); router.get('/', function(req, res) { res.send('hello, express'); }); module.exports = router;
其中router为express.Router() 的一个实例,这是非常重要的。
对于routes下的users也是一样的:
var express = require('express'); var router = express.Router(); router.get('/:name', function(req, res) { res.send('hello, ' + req.params.name); }); module.exports = router;
这样就可以很好的管理路由了,当然,不难看出,实际项目中,我们并不是真的只要这两个路由,而是根据你的项目,可能除了 index、 users, 还有其他的文件,只要保证一个路径对应一个路由文件即可,这样便可很好地管理路由了。
重要的是需要在 app.js 中使用 app.use() 挂载到不同的路径上。
易错点: 在使用 app.use() 时,第一个参数是一个相对的路径,然后使用第二个参数,即routes下的文件时, router.get("/"),这又是一个路径,最终表现在url上是两者的综合路径(叠加路径),比如app.use('/reg', index); 其中在index下的路由文件中设置的是 router.get('/reg', function (req, res) {}) , 那么最终表现出来的就是localhost:8888/reg/reg 这样的路由,这样才能正确访问, 否则就会出错。
(补)debug模块的使用。(参考教程)
在上面的例子中,我们使用 set DEBUG=myapp:* & npm start, 其中用到了debug模块, 实际上debug模块是怎么使用的呢?
nodejs的调试有很多,这里主要介绍debug模块调试,首先npm init 、npm install debug --save, 新建app.js文件,其内容如下:
var debug = require("debug")("mydebug:http"), work = require("./work"), http = require("http"); http.createServer(function (req, res) { debug(req.method + " " + req.url); res.end("hello \n"); }).listen(3000, function () { debug("listening"); });
然后建立work.js,内容如下:
var debug = require("debug")("mydebug:work"); setInterval(function () { debug("doing some work @ %s - %s", new Date().toString(), "with supervisor"); }, 2000);
(注意:如果要debug, 就必须要在当前目录下存在 npm-debug.log 日志文件)
可以看到,这两个模块中我都使用了 debug 模块。 运行 set DEBUG=mydebug:* & node app.js ,如下:
即这里的debug语句就相当于console.log(),然后对于不同的debug,会显示不同的显色, 而mydebug是我设置的debug名称,也可以是其他的。
开启时使用的是 set DEBUG=mydebug:* & node app , 也就是说我们运行了所有的(*)debug模块,如果我们只想运行work模块,而不运行http模块,可以像下面这样:
set DEBUG=mydebug:work & node app
我们可以将&理解为并且的意思。(为什么不是&&呢?)
(补)npm start的使用原理
这里实际上是 npm run start 的简写。 参考 http://javascript.ruanyifeng.com/nodejs/packagejson.html
这里的npm start启动的是bin目录下的www,也就是说使用 express-generator 的默认的入口是 bin 下的 www, 而不是app.js ,目前很多项目都是如此, 我们可以在 package.json中进行设置,如 express-generator 中的设置如下:
{ "name": "myapp", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "body-parser": "~1.17.1", "cookie-parser": "~1.4.3", "debug": "~2.6.3", "express": "~4.15.2", "jade": "~1.11.0", "morgan": "~1.8.1", "serve-favicon": "~2.4.2" } }
其中的 scripts 中设置了start(入口文件)为 node ./bin/www ,即当启动项目时,实际上输入的是 node ./bin/www ,只是这样设置的好处在于更加方便管理。容易理解。
比如我们创建一个文件,npm init , 创建 app.js,内容如下:

var http = require("http"); http.createServer(function (req, res) { res.writeHead(200, {"Content-Type": "text/plain; charset=utf8"}); res.write("hello"); res.end(); }).listen(8888, function () { console.log("Server is running at port 127.0.0.1:8888"); });
然后node app即可启动这个项目,但是app就在这当然比较好启动,可如果入口文件藏的很深呢? 比如./bin/www就深了一层,每次 node ./bin/www可能会比较麻烦,所以在 package.json 中的scripts下添加了 start 选项,对于这个项目我们也可以添加,如下:
{ "name": "starttest", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node app.js" }, "author": "", "license": "ISC" }
即添加start项,每次npm start 就相当于 node app.js,这样的效果是一样的。
我们可以使用 npm run start 和 npm run test 都是可以执行的。 参考阮一峰教程。
(补:)package.json中的dependencies是什么? 作用如何? 和devDependencies的区别是什么?
即项目依赖项,用于告诉我们这个项目依赖了哪些模块,但这不是主要作用, 主要作用是当我们将项目上传到服务器时,可以不用上传 node_modules ,而只用上传基础文件即可, 服务器端可以直接在根目录下 npm install ,然后就可以安装package.json中所有的依赖模块了。
dev,顾名思义,是开发的意思, 而dependencies是指生产中所需要的依赖项, devDependencies 可能包括一些生产中不需要而仅在开发中需要的调试模块。
3. 路由见了很多,到底什么是路由? 句柄又是什么? 路由中有哪些常用的响应方法?
简单的理解,路由就是 路径 + 一个http方法 + 一些句柄。 下面就是一个路由文件:
// 对网站首页的访问返回 "Hello World!" 字样 app.get('/', function (req, res) { res.send('Hello World!'); }); // 网站首页接受 POST 请求 app.post('/', function (req, res) { res.send('Got a POST request'); }); // /user 节点接受 PUT 请求 app.put('/user', function (req, res) { res.send('Got a PUT request at /user'); }); // /user 节点接受 DELETE 请求 app.delete('/user', function (req, res) { res.send('Got a DELETE request at /user');
其中app是一个express实例, 同时包含了路径(/ 、user)和http方法(get、post、delete、put)。
有时候我们还会见到app.all() ,这是指不论是什么方法,只要路径对了,就会执行后面的句柄。
那么什么是句柄呢? 其实句柄就是指其中的语句, 执行的函数。。。
而路由中一定是有响应方法的,比如之前一直使用的 res.send() 这样可以把其中的内容返回给页面,另外,还有下面的一些方法:
其中 res.end() 也比较常用,表示发送结束了。 res.download() 方法接受一个参数是文件的相对路由, 一旦满足路由,就会下载文件。 res.json() 即接受一个json字符串。如下:
var express = require("express"); var fs = require("fs"); var app = express(); app.get("/", function (req, res) { res.json('{"name": "John Zhu"}'); res.end(); }); app.get("/login", function (req, res) { res.download("./test.txt"); }); app.listen(3000, function () { console.log("Server is running at localhost:3000"); });
另外,对于相同的路径,根据不同的请求方法给出不同的句柄,我们应该怎么实现呢? 可以是下面这样:
app.get("/", function (req, res) { res.send("get"); }); app.post("/", function (req, res) { res.send("post"); }); app.delete("/", function (req, res) { res.send("delete"); }); app.put("/", function (req, res) { res.send("put"); });
但是这样显然代码是冗余的,并且容易造成拼写错误,如果使用 app.route() 使用链式定义会更好,如下:
app.route("/") .get(function (req, res) { res.send("get"); }) .post(function (req, res) { res.send("post"); }) .delete(function (req, res) { res.send("delete"); }) .put(function (req, res) { res.send("put"); });
这样更容易查看并且不容易出错。
4. express中静态文件是什么?
错! 静态文件是node中的概念,而不仅仅是express中的。 它的作用就是托管静态文件。 什么是静态文件呢? 比如我们看到一个网站上(如网易)的一个图片,然后复制图片地址,如http://img3.cache.netease.com/photo/0001/2017-04-21/CII56AJH19BR0001.jpg ,打开这个连接,发现这就是一个图片, 而这,就是静态文件。 如我们再引入图片、css、js等时,这些文件都是静态文件,由此可知,静态文件的重要性。
在使用express-generator生成应用的时候,我们就可以在那个架构中看到 public 文件,这个文件就是静态文件,其中包含了images、javascripts、stylesheets。 使用如下:
app.use(express.static("public"));
重要声明: 其中app是一个express实例,而use就代表使用一个中间件。即使用express.static()中间件。其中一定是express而不是app,这是需要格外注意的地方。
5. 刚刚也提到了中间件这个概念,那么到底什么是中间件? 怎么理解中间件中的next()方法?
在知乎上有这么一个回答,就照搬过来吧~
毫无疑问,这里的中间件的定义是便于我们理解的,我们看看官网上是怎么说的吧~
Express 是一个自身功能极简,完全是由路由和中间件构成一个的 web 开发框架:从本质上来说,一个 Express 应用就是在调用各种中间件。
中间件(Middleware) 是一个函数,它可以访问请求对象(request object (
req
)), 响应对象(response object (res
)), 和 web 应用中处于请求-响应循环流程中的中间件,一般被命名为next
的变量。
在express中可以使用下面的几种中间件:
应用级中间件:即我们使用的app.use() 和 app.get()之类的中间件。 通过这个我们不难理解,express的确完全是使用中间件搭建起来的。 因为app.METHOD()在服务器端语言node中使用的很多。
路由级中间件:即app.Router()的中间件
错误处理中间件:即函数的参数必须要四个,分别是 error、req、res和next。 error就是用来处理错误的。
内置中间件:express中唯一内置的中间件就是 express.static()了。
第三方中间件:如cookie-parser 这样的中间件就是第三方中间件。 还有body-parser也是的。
6. 如何在Express中使用模板引擎?
说明:模板引擎有很多,比如 jade (下面主要说的)、 ejs (也是非常常用的)等等很多。
两个步骤就可以让express来渲染模板文件:
第一: 添加放置模板的目录views, 然后app.set('views', './views');
第二: 添加模板引擎即views engine, 然后app.set('view engine', 'jade');
当然前提条件是有相应的模板引擎安装包:
npm install jade --save
之前我们使用 express-generator 的时候就可以发现目录下已经有了 views 目录, 同时 package.json 中也是有依赖项jade 的, 利用之我们可以看到在app.js中它是这样设置的。
// view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade');
当然这是一样的, set函数接受两个参数,第一个是要设置的东西,这里是模板文件(views), 第二个参数是一个路径, 不难理解,path.join() 就是为了将两个path组合到一起 。 当然,前提是引入path模块。
对于第二句使用jade模板引擎都是一样的。
然后我们再 views 下面生成以及模板文件, 如generator生成的文件就是 index.jade、layout.jade、 error.jade 这三个模板引擎。其中layout.jade如下所示:
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') body block content
这个模板引擎的特有语法, 更加简洁。 在body下有一个block content, 这时就要根据不同的状态使用 index.jade 和 error.jade了, 如index.jade内容如下:
extends layout block content h1= title p Welcome to #{title} #{title}
很容易看出来它是对 layout.jade 的扩展, 其定义了block content的内容, 其中的title就是可以替换的变量,后面会讲到。而error.jade的内容如下:
extends layout block content h1= message h2= error.status pre #{error.stack}
同样,这也是对于layout.jade 的扩展。
在哪里控制这个渲染的呢? 显然这些都与路由有关,所以在routes下的index.js中可以看到:
var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { res.render('index', { title: 'Express' }); }); module.exports = router;
即使用了res.render()渲染index这个模板引擎(在扩展layout的基础上),并给这个模板引擎传递参数title: 'Express', 这是以对象的形式传递的所以说我们还可以传递更多的参数。
更一般的理解: 一旦get方法访问了主页,就会将index.jade模板引擎渲染为html页面返回给用户。
开头就说了,模板引擎有很多,ejs 就是其中一种, 因为它在使用起来非常简单, 并且与 express 集成良好, 所以我们选用 ejs . (ejs官方文档)
在 express-generator 中自动生成的是jade, 所以如果不实用jade而是使用 ejs 的话,我们就要单独安装了,如下所示:
npm install ejs --save
然后,我们需要在 app.js 中添加下面的代码:
app.set('views', path.join(__dirname, 'views')); app.set('views engine', 'ejs');
app.set('view engine', 'ejs');
也就是说使用views作为放置模板的目录。 使用ejs作为模板引擎。
注意: 设置路径时是 views ,因为模板不止一个,但是在设置模板引擎时,一定是 view engine ,而不能加s, 否则就会报错。
另外,在app.js中不需要 require("ejs") ,如果require()了也不会报错。
接下来开始设置模板, 在 views 下面添加 user.ejs,其中内容如下:
<!DOCTYPE html> <html> <head> <style type="text/css"> body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;} </style> </head> <body> <h1><%= name.toUpperCase() %></h1> <p>hello, <%= name %></p> </body> </html>
这就是ejs的格式, 使用 <% 变量 %>将变量包裹起来。可以看到,在模板中我使用了 name.toUpperCase() 这样的js语句。
修改 routes下的 user.js 内容如下:
var express = require('express'); var router = express.Router(); router.get('/:name', function(req, res) { res.render('users', { name: req.params.name }); }); module.exports = router;
这样就可以成功渲染一个html页面了。即通过 res.render() 来渲染 ejs 模板, res.render() 的第一个参数是模板的名字,这里的user会匹配 views/user.ejs , 第二个参数是传给模板的数据,这里传入了name,那么在模板中就可以使用 name 这个变量了, 所以res.render() 的作用就是将模板和数据结合成HTML,同时在响应头中设置 {"Content-Type: text/html"} 告诉浏览器我渲染的是一个html页面,而不是文本。
补充说明: ejs有下面几种常用的标签:
- <% code %> 运行js代码,不输出
- <%= code %> 显示转义后的html内容
- <%- code %> 显示原始html内容
下面的例子解释了 <% code %>的用法:
DATA:
supplies: ['mop', 'broom', 'duster']
EJS TEMPLATE:
<ul> <% for(var i=0; i<supplies.length; i++) {%> <li><%= supplies[i] %></li> <% } %> </ul>
RESULT:
<ul> <li>mop</li> <li>broom</li> <li>duster</li> </ul>
ejs --- include
我们使用模板通常并不是一个页面对应一个模板,这样模板的优势就失去了。而是把模板拆成可以复用的模板片段组合使用 (这正是我想要的)。
比如我在views下新建了 header.ejs 和 footer.ejs, 并修改了 user.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/user.ejs
<%- include('header') %> <h1><%= name.toUpperCase() %></h1> <p>hello, <%= name %></p> <%- include('footer') %>
即我们通过 incluede 方式引入了footer和header,这样,如果文件多了,利用率会更高一些。 注意: 其中 <%- ... %> 表示使用原始数据,即原来是啥就是啥。
说明: 拆分模板组件的两个好处
- 模板可以复用,减少重复代码
- 主模板更加清晰。
7.怎么理解错误处理? 如何进行错误处理?
错误处理? 即请求发生错误,或者是响应错误时,给出一定的处理方案: 如给用户提示错误信息等等。。。
错误处理也是一种中间件,之前我们就说过,express框架就是使用一大推中间件堆积起来的,错误处理中间件是一个函数, 必须有四个参数,分别是 err req res next, 值得注意的是,我们必须要在其他中间件定义完了之后,然后在定义错误处理中间件。 如下:
var bodyParser = require('body-parser'); var methodOverride = require('method-override'); app.use(bodyParser()); app.use(methodOverride()); app.use(function(err, req, res, next) { // 业务逻辑 });
我们可以自己测验一下,如下所示:
var express = require("express"); var app = express(); app.get('/', function (req, res) { // 这个函数没有定义会发生错误。 god(); }); app.get('/login', function (req, res) { res.send("登录成功!"); }); app.use(function (err, req, res, next) { res.send("错误发生:" + err); }); app.listen(3000, function () { console.log("server is running at localhost:3000..."); });
在这里, 我们在‘/’定义了一个 get 请求,然后执行一个没有定义的函数,那么这一定会出错,然后不过不使用 app.use(function (err, req, res, next) {...}) 那么错误界面就会很乱甚至导致后台崩溃,但是如果我们使用了异常处理中间件, 就会发现, 可以通过它捕获到请求。 值得注意的是: 异常处理中间件一定要放在最后(listen之前), 这样就可以成功的异常处理了。
如下:
错误发生:ReferenceError: god is not defined
当然我们也可以打印出 错误栈(error stack), 即
res.send("错误发生:" + err.stack);
效果如下:
8. nodejs作为后台语言,怎么集成数据库?
的确,后台语言大半时间也是为了和后台打交道的,所以数据库的连接和使用格外重要。 要为nodejs连接数据库,只需要添加相应的驱动即可。下面是一些常用的数据库node模块。
这里主要讲MySQL 和 MongoDB,如下所示:
MySQL --- 首先安装mysql模块:
npm install mysql
然后连接数据库:
var mysql = require('mysql'); var connection = mysql.createConnection({ host : 'localhost', user : 'dbuser', password : 's3kreee7' }); connection.connect(); connection.query('SELECT 1 + 1 AS solution', function(err, rows, fields) { if (err) throw err; console.log('The solution is: ', rows[0].solution); }); connection.end();
MongoDB --- 首先安装mongoskin模块:
npm install mongoskin
然后连接数据库:
var db = require('mongoskin').db('localhost:27017/animals'); db.collection('mamals').find().toArray(function(err, result) { if (err) throw err; console.log(result); });
8. app.get("/logim/:name", function () {}); 是什么意思? 那里的:的作用是什么?
其中:name是占位符的意思,如:
app.get("/login/:name", function (req, res) { res.send(req.params.name); });
如果我们输入 localhost:8888/login/hhh ,那么浏览器上就会显示hhh,因为我们可以通过 req.params.name 读取到这个占位符。
推荐github项目: https://github.com/nswbmw/N-blog/blob/master/book/3.3%20%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E.md
结束