NodeJS入门实践第二讲Express的使用

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的值,关闭浏览器就失效。

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

推荐阅读更多精彩内容