NodeJs提供了的方法处理起来比较麻烦,在使用nodejs时有很多现成的框架帮助开发人员,其中最简单的一个server框架就是Express,express的安装方式有两种,一种是在项目文件夹中安装express,另外一个是通过express的generator来生成一个express的骨架,我们先从最原始的方式来了解express中的几个比较核心的概念
基于原始的方式使用express
首先创建一个项目,之后初始化为npm类型的项目,要生成node的基本项目,需要使用npm init
进行初始化,初始化会输入一些基本的项目信息,这个步骤和java的maven非常类似,等于创建了maven的pom文件,而对于node而言是创建了package.json文件。
> npm init
About to write to E:\study\nodejs_2018\11_express\package.json:
{
"name": "express_first",
"version": "1.0.0",
"description": "express init",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "konghao",
"license": "ISC"
}
下一步使用npm安装express,由于express仅仅只是存在在当前项目中,所以安装不用使用-g进行全局安装,但是需要增加--save的参数,这个表示会把这些信息添加到package.json的依赖中。
E:\study\nodejs_2018\11_express>npm install express --save
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN express_first@1.0.0 No repository field.
+ express@4.16.2
added 48 packages in 24.734s
{
"name": "hello_express",
"version": "1.0.0",
"description": "express begin",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"author": "konghao",
"license": "ISC",
"dependencies": {
"express": "^4.16.2",
}
}
大家会发现package.json中有了一个dependencies,添加了express的依赖。在这个文件我进行两个设置,在scripts中增加了start的脚本,该脚本使用node app.js来运行一个文件,此时app.js是我们的核心的服务器文件,添加了这个脚本之后,可以直接使用npm start
运行该脚本。
下来创建app.js来运行一个express的应用
var express = require("express")
var app = express();
app.get("/hello",function (req,resp) {
resp.send("hello express!");
});
app.listen(3000,function () {
console.log("server start!!");
});
已上程序中头两行创建了express的对象,app.get(xx)这个和nodejs的路由一模一样,表示接收了get请求,之后使用function回调函数进行处理,通过resp的send方法可以输出数据到网页,app.listen(xx)表示监听端口,等于nodejs中的createServer。已上程序只要运行之后在浏览器中输入localhost:3000/hello将会显示hello express的文本在浏览器中。这就是一个简单的express的应用,注意express是一个完全基于路由的架构,下面我们来详细了解express中最核心的一个概念:中间件。
express的中间件
express是一个自上而下进行执行的运用,在具体讲解之前,我们加入一个应用包,我们现在的程序只要改动app.js都得开发人员自动关闭和启动nodejs。在npm中有一个叫做nodemon的框架,可以帮助我们自动重新启动,nodemon建议大家安装成全局类型,这样方便所有的应用都能使用,使用下面的脚本安装
npm install nodemon -g
安装完成之后只要通过nodemon app.js
此时只要项目中的文件有变化它会自动启动,为了方便这个操作,我将package.json中start的脚本进行的了简单的修改
....
"scripts": {
"start": "nodemon app.js"
},
...
下面我们来修改app.js中的一些代码
...
app.get("/:username",function(req,resp){
resp.send("hello:"+req.params.username);
});
app.get("/hello",function (req,resp) {
resp.send("hello express!");
});
...
第一个get表示获取/xxx,这个路径会被解析成为username的参数,通过req可以显示这个参数。此时当我们输入路径localhost:3000/hello,express在读取代码的时候会首先匹配到,它会将hello认为是username,所以浏览器中会显示hello:hello
第二个/hello的路由就不会再被匹配了,express的执行方式就是这样的。只要匹配到一个路由,如果没有做任何特殊的处理,处理完就停止了。
上面我们提到的特殊处理就是中间件的一种形式,如果我们在第一个函数中增加一个next的参数,就能够做一些特殊处理,大家看代码
app.get("/:username",function(req,resp,next){
// resp.send("hello:"+req.params.username);
console.log(req.params.username);
next();
});
app.get("/hello",function (req,resp) {
resp.send("hello express!");
});
此时第一个不使用send,而是使用console.log输出了一下username。执行完成之后使用了next(),这表示express会继续向下执行请求,所以第二个路由/hello也会被执行,所以将会输出hello express。这就是express中间件的一个核心操作方式,用户可以通过next()来确定路由请求是否往下提交。下面我们来看express另一个重要的函数,use函数。
开始我们已经多次强调了express的所有操作都是基于路由进行的,其中get方法处理get请求,post方法处理post请求,而还有一个特殊的use方法,也是处理请求,但它的特殊性在于,它会自动匹配满足条件的所有请求,首先就刚才讲的例子来看,我们的地址如果是localhost:3000/hello/abc。此时有两个路径,express一个都不会进行匹配。
此时如果将get改成use,express会自动匹配所有/hello开头的请求
app.use("/hello",function (req,resp) {
resp.send("hello express!");
});
我们发现,只要路径是以/hello开头的所有路径都会被匹配如: /hello/abc,/hello/abc/a/b等等,这些都会被匹配。此时如果有个地址是app.use("/",function(req,resp))
那是不是意味着所有的请求都会通过这个函数,此时如果不使用next,那意味着,请求到这个就终止了,但是我们可以通过next将请求往下执行。如果地址是/,我们可以省略第一个参数使用app.use(function(req,resp))
来替换。
以上操作就提供了一种方式,让我们可以让express执行我们的某个模块,这个模块我就将其称之为中间件,接下来我们自己编写一个处理静态资源文件的中间件。
var express = require("express");
var url = require("url");
var fs = require("fs");
var app = express();
// app.get("/:username",function(req,resp){
// resp.send("hello:"+req.params.username);
// });
function handleStatic(req,resp,next) {
var pathname = url.parse(req.url).pathname;
if(pathname=="/") pathname = "index.html";
fs.readFile("./publics/"+pathname,null,function (err,data) {
if(err) {
//文件不在,直接next到后面的请求
next();
} else {
resp.writeHead(200,{"Content-type":"text/html;charset=utf-8"});
resp.write(data);
resp.end();
}
});
}
app.use(handleStatic);//启动handleStatic的函数,所有请求都会调用
app.get("/:username",function(req,resp,next){
// resp.send("hello:"+req.params.username);
console.log(req.params.username);
next();
});
app.use("/hello",function (req,resp) {
resp.send("hello express!");
});
app.listen(3000,function () {
console.log("server start!!");
});
handleStatic就等于一个我们自定义的中间件。通过这个例子我相信大家对express的处理流程已经有了一个清晰的理解了,这种中间件的方式为我们的代码带来了极大的灵活性,我们可以非常轻松的往express中添加和删除各种模块。下面我们来看视图渲染。
视图渲染之pug
express默认提供的视图是jade,现在已经变成了pug,我们首先安装pug的依赖
npm install pug --save
此时会在package.json中加入pug的依赖
"dependencies": {
"express": "^4.16.2",
"pug": "^2.0.0-rc.4"
}
pug是nodejs默认的视图引擎,我们只要进行简单的配置即可使用
//说明的视图的路径是根路径加上views文件夹
app.set("views",path.join(__dirname,"views"));
//说明视图引擎的文件名称的后缀名是pug,注意,有些模板是jade。pug就是新版的jade
app.set("view engine","pug");
app.get("/test",function(req,resp){
var users = [{"username":"foo",age:12}
,{"username":"bar",age:33}
];
//传入了两个参数
resp.render("test",{title:"hello pug",users:users});
});
上面的代码设定了视图引擎是pug。它会自动在views目录中找xx.pug来渲染,/test这个路由使用的方法是resp.render()
这就表示会渲染给一个视图"test.pug"文件,并且传递了title和users两个数据,接下来看看pug的编写方法,这个比较特殊,它是使用tab的缩进来表示html内容,这里只是简单的介绍,大家如果有兴趣可以花点时间去研究一下,视图的研究无非就是从几个点入手(如何传数据,如何展示成html,如何写选择,如何写循环,宏定义)。下面的代码我们定义了模板layout.pug。将其他模板继承layout.pug可以比较方便的实现基本模块。
doctype html
html
head
title #{title}
body
h1 pug的基本例子
block content
hr
p(style="text-align:center") copyright
已上代码定义了模板文件,注意block content
就是要在其他地方嵌套的内容,下面看看test.pug的操作
extends layout
block content
ul
each u,i in users
li=i+"."+u.username+"---"+u.age
test.pug中继承了layout,并且将block content中的内容替换为一个无序列表,使用了each来遍历users这个渲染视图的参数。
关于pug大家可以自行学习,也非常简单。下面我们会介绍另外一个视图模板引擎handlebars。
视图模板引擎handlebars
pug引擎的写法和我们熟悉的html非常不一样,node提供了多种模板引擎,handlebars就是其中一个基于html格式的引擎,下面我们看看如何使用,首先卸载pug引擎
npm remove pug --save
下面安装handlebars,我们安装的是支持express的版本,这个和express整合起来要简单一些,如果不使用express,可以直接安装handlebars。
npm install express-handlebars --save
之后需要在app.js中注册这个模板引擎,由于handlebars不是node的默认引擎,所以需要程序员注册这个引擎
//注册hbs引擎,说明引擎的后缀名是hbs,默认的模板名称是layout.hbs,目录在views中的layouts中
app.engine("hbs",hbs({extname:"hbs",defaultLayout:"layout",layoutDir:__dirname+"/views/layouts"}))
//说明的视图的路径是根路径加上views文件夹
app.set("views",path.join(__dirname,"views"));
//说明视图引擎的文件名称的后缀名是pug,注意,有些模板是jade。pug就是新版的jade
app.set("view engine","hbs");
接下来看看layout.hbs的写法,这个和html如出一辙
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{title}}</title>
</head>
<body>
<h1>{{title}}</h1>
{{{body}}}
<hr>
<p>copyright</p>
</body>
</html>
需要注意的是两个{{}}的,表示获取传过来的值,而三个{{{}}}表示引入具体的内容,而且解析html。这里的{{{body}}}表示具体的模板内容,我们也可以在app.js中设置不使用模板resp.render("test",{title:"hello handlebars",users:users,layout:false}); 这就表示不使用layout。下面看看test.hbs文件的写法
<ul>
{{#each users}}
<li>{{this.username}}---{{this.age}}</li>
{{/each}}
</ul>
通过each遍历了users。使用handlebars是不是要熟悉一些呢?
使用express-generator创建项目
现在我们应该已经对express有了大致的了解,下面就可以使用express-generator来生成express项目了,这将会极大的简化我们的开发操作,首先将express-generator安装到全局中。
npm install express-generator -g
安装完成之后使用
express 13_express
此时会完成express骨架的创建,首先看看package.json文件
{
"name": "13-express",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"body-parser": "~1.18.2",
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"express": "~4.15.5",
"jade": "~1.11.0",
"morgan": "~1.9.0",
"serve-favicon": "~2.4.5"
}
}
看看scripts中的start脚本,表示服务器的启动文件不是app.js而是bin路径中的www文件,我们可以将其修改为nodemon ./bin/www,之后看看依赖包,首先body-parser用来解析http请求的,可以非常轻松的解析json数据格式;cookie-parse用来解析cookie请求,debug用来进行调试,jade就是模板引擎,可以将其修改为handlebars,morgan是日志组件,serve-favicon用来处理图标,此时这些包并没有安装到我们的项目中的,需要使用install进行安装
npm install
之后移除jade,可以安装pug
npm remove jade --save
npm install pug --save
看看最重要的app.js
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
//定义了两个路由模块
var index = require('./routes/index');
var users = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
//静态文件的处理,所有的静态文件在public中
app.use(express.static(path.join(__dirname, 'public')));
//路由处理,/会交给index模块处理
app.use('/', index);
// users开头的会交给user模块处理
app.use('/users', users);
//异常处理
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
我们能够找到一些熟悉的身影,如模板引擎,我们可以注意到它已经帮我们处理了静态文件,在public文件夹中。而路由引擎是由两个独立的模块来实现的而在router的文件夹,简单看看路由文件index.js
var express = require('express');
var router = express.Router();
/* 此处没有使用use而是使用get */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
注意路由中使用的是get,这样只会捕获根目录的信息。可以在这些js文件中专门增加自己的路由信息。至于其他模块,大家在使用中自然而然就会了。关键是主要的思路,下面我们会给大家介绍session和表单的validate的模块。
cookie-parser和body-parser模块
首先看一个实例,通过表单提交一个post请求,首先新建一个项目,导入express的模块,编写如下的代码:
var express = require("express");
var app = express();
app.get("/",function(req,resp) {
resp.sendFile("index.html",{root:__dirname+"/publics"});
});
app.listen(3000,function (req,resp) {
console.log("server start!");
});
在app的get方法中,用了一个新的方法resp.sendFile,该方法类似于fs.readFile方法,该方法会渲染根目录下的publics中的index.html文件,该文件中创建了一个表单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
Hello app!
<form action="/form?hello=abc" method="post">
<input type="text" name="username"/><br/>
<input type="submit"/>
</form>
</body>
</html>
form的action中通过get传递了一个参数hello,表单是通过post的请求提交的,接下来看看处理的代码
app.post("/form",function (req,resp) {
resp.send(req.query.hello+","+req.query.username);
});
程序中通过req.query来获取浏览器的get参数,我们会发现req.query.username取不到任何值,这说明req.query仅仅只能获取get的请求参数,那该如何获取post的请求参数呢?此时就需要使用body-parser的中间件来执行。首先安装body-parser中间件
E:\study\nodejs_2018\15_body>npm install body-parser --save
npm WARN 15_body@1.0.0 No description
npm WARN 15_body@1.0.0 No repository field.
+ body-parser@1.18.2
updated 1 package in 3.891s
看看源代码,和原来的差不多,但是需要引入body-parser的中间件
var express = require("express");
var bodyParser = require("body-parser");
app.use(bodyParser());
app.post("/form",function (req,resp) {
//使用req.body来获取name为username的值
resp.send(req.query.hello+","+req.body.username);
});
此时form中的内容就可以通过req.body来读取。通过这个实例大家应该清楚body-parser的作用了,接下来我们来看看如何处理cookie,cookie和jsp中的cookie是一样,我们需要cookie-parser模块支持,首先安装cookie-parser的中间件。
npm install cookie-parser --save
首先需要引入cookie-parser,并且引入cookie-parser,编写一个login的路由
var cookieParser = require("cookie-parser");
//使用cookie-parser的中间件。
app.use(cookieParser());
//基于get请求的login
app.get("/login",function(req,resp){
resp.sendFile("form.html",{root:__dirname+"/publics"});
});
这是get请求,访问login会直接访问form.html
<form action="/login" method="post">
username:<input type="text" name="username"/><br/>
password:<input type="password" name="password"/><br/>
<input type="submit"/>
</form>
通过post请求提交给/login的路由
//基于post请求的login
app.post("/login",function (req,resp) {
var username = req.body.username;
var password = req.body.password;
if(username=="admin"&&password=="123") {
//存储了cookie,时间是60分钟
resp.cookie("user",{username:username,password:password},{maxAge:600000,httpOnly:true});
}
resp.redirect("/loginOk");
});
如果用户名和密码正确通过resp.cookie方法存储cookie,cookie的名称是user,存储了一个username和password的对象,有效时间是60分钟,然后是基于http请求的存储。最后通过resp.redirect("/loginOk"),这其实就是jsp中的服务器跳转。最后看看loginOk是如何读取cookie的
app.get("/loginOk",function (req,resp) {
var cookies = req.cookies.user;
if(cookies) {
resp.send("hello:"+cookies.username);
} else {
resp.send("no cookies found!");
}
});
通过req来读取cookies的信息。
在这一小节结束之前我们需要再次总结req获取参数的三种方式:
1、req.params.xx 这是获取路径中的参数
2、req.query.xx 这是获取get请求的参数
3、req.body.xx 这是通过body-parser来获取form表单中的post请求。
express的session
express同样也支持session,需要express-session的支持,首先通过npm安装express-session
npm install express-session --save
在app.js中引入这个中间件并且创建session
var session = require("express-session");
app.use(session({
secret: 'a4f8071f-c873-4447-8ee2'
}));
secret是一个服务器端的签名,这个字符串可以随便设定,之后通过req.session来写和读取session,session的操作非常简单
app.get("/session",function (req,resp) {
req.session.username = "admin";
req.session.nickname = "超级管理员";
resp.redirect("/sessionOk");
});
app.get("/sessionOk",function (req,resp) {
resp.send("session ok:"+req.session.username+"("+req.session.nickname+")");
});
通过/session的路由来设定session,在sessionOk中来读取session的值。session如果没有设定特殊的cookie的值,关闭浏览器就失效。