NodeJs提供了的方法處理起來比較麻煩蒸其,在使用nodejs時有很多現(xiàn)成的框架幫助開發(fā)人員道盏,其中最簡單的一個server框架就是Express雕什,express的安裝方式有兩種斤儿,一種是在項目文件夾中安裝express飞盆,另外一個是通過express的generator來生成一個express的骨架,我們先從最原始的方式來了解express中的幾個比較核心的概念
基于原始的方式使用express
首先創(chuàng)建一個項目忘巧,之后初始化為npm類型的項目恒界,要生成node的基本項目,需要使用npm init
進(jìn)行初始化砚嘴,初始化會輸入一些基本的項目信息十酣,這個步驟和java的maven非常類似,等于創(chuàng)建了maven的pom文件际长,而對于node而言是創(chuàng)建了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僅僅只是存在在當(dāng)前項目中工育,所以安裝不用使用-g進(jìn)行全局安裝虾宇,但是需要增加--save的參數(shù),這個表示會把這些信息添加到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",
}
}
大家會發(fā)現(xiàn)package.json中有了一個dependencies嘱朽,添加了express的依賴。在這個文件我進(jìn)行兩個設(shè)置怔接,在scripts中增加了start的腳本搪泳,該腳本使用node app.js來運行一個文件,此時app.js是我們的核心的服務(wù)器文件扼脐,添加了這個腳本之后岸军,可以直接使用npm start
運行該腳本。
下來創(chuàng)建app.js來運行一個express的應(yīng)用
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!!");
});
已上程序中頭兩行創(chuàng)建了express的對象瓦侮,app.get(xx)這個和nodejs的路由一模一樣艰赞,表示接收了get請求,之后使用function回調(diào)函數(shù)進(jìn)行處理肚吏,通過resp的send方法可以輸出數(shù)據(jù)到網(wǎng)頁方妖,app.listen(xx)表示監(jiān)聽端口,等于nodejs中的createServer罚攀。已上程序只要運行之后在瀏覽器中輸入localhost:3000/hello將會顯示hello express的文本在瀏覽器中吁断。這就是一個簡單的express的應(yīng)用,注意express是一個完全基于路由的架構(gòu)坞生,下面我們來詳細(xì)了解express中最核心的一個概念:中間件。
express的中間件
express是一個自上而下進(jìn)行執(zhí)行的運用掷伙,在具體講解之前是己,我們加入一個應(yīng)用包,我們現(xiàn)在的程序只要改動app.js都得開發(fā)人員自動關(guān)閉和啟動nodejs任柜。在npm中有一個叫做nodemon的框架卒废,可以幫助我們自動重新啟動沛厨,nodemon建議大家安裝成全局類型,這樣方便所有的應(yīng)用都能使用摔认,使用下面的腳本安裝
npm install nodemon -g
安裝完成之后只要通過nodemon app.js
此時只要項目中的文件有變化它會自動啟動逆皮,為了方便這個操作,我將package.json中start的腳本進(jìn)行的了簡單的修改
....
"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的參數(shù)电谣,通過req可以顯示這個參數(shù)。此時當(dāng)我們輸入路徑localhost:3000/hello抹蚀,express在讀取代碼的時候會首先匹配到剿牺,它會將hello認(rèn)為是username,所以瀏覽器中會顯示hello:hello
第二個/hello的路由就不會再被匹配了环壤,express的執(zhí)行方式就是這樣的晒来。只要匹配到一個路由,如果沒有做任何特殊的處理郑现,處理完就停止了湃崩。
上面我們提到的特殊處理就是中間件的一種形式,如果我們在第一個函數(shù)中增加一個next的參數(shù)接箫,就能夠做一些特殊處理攒读,大家看代碼
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列牺。執(zhí)行完成之后使用了next()整陌,這表示express會繼續(xù)向下執(zhí)行請求,所以第二個路由/hello也會被執(zhí)行瞎领,所以將會輸出hello express泌辫。這就是express中間件的一個核心操作方式,用戶可以通過next()來確定路由請求是否往下提交九默。下面我們來看express另一個重要的函數(shù)震放,use函數(shù)珍坊。
開始我們已經(jīng)多次強調(diào)了express的所有操作都是基于路由進(jìn)行的恤浪,其中g(shù)et方法處理get請求千扔,post方法處理post請求沸毁,而還有一個特殊的use方法勿璃,也是處理請求杖狼,但它的特殊性在于充石,它會自動匹配滿足條件的所有請求榛臼,首先就剛才講的例子來看耳峦,我們的地址如果是localhost:3000/hello/abc恩静。此時有兩個路徑,express一個都不會進(jìn)行匹配。
此時如果將get改成use驶乾,express會自動匹配所有/hello開頭的請求
app.use("/hello",function (req,resp) {
resp.send("hello express!");
});
我們發(fā)現(xiàn)邑飒,只要路徑是以/hello開頭的所有路徑都會被匹配如: /hello/abc,/hello/abc/a/b等等,這些都會被匹配级乐。此時如果有個地址是app.use("/",function(req,resp))
那是不是意味著所有的請求都會通過這個函數(shù)疙咸,此時如果不使用next,那意味著风科,請求到這個就終止了撒轮,但是我們可以通過next將請求往下執(zhí)行。如果地址是/丐重,我們可以省略第一個參數(shù)使用app.use(function(req,resp))
來替換腔召。
以上操作就提供了一種方式,讓我們可以讓express執(zhí)行我們的某個模塊扮惦,這個模塊我就將其稱之為中間件臀蛛,接下來我們自己編寫一個處理靜態(tài)資源文件的中間件。
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的函數(shù)浊仆,所有請求都會調(diào)用
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的處理流程已經(jīng)有了一個清晰的理解了豫领,這種中間件的方式為我們的代碼帶來了極大的靈活性抡柿,我們可以非常輕松的往express中添加和刪除各種模塊。下面我們來看視圖渲染等恐。
視圖渲染之pug
express默認(rèn)提供的視圖是jade洲劣,現(xiàn)在已經(jīng)變成了pug,我們首先安裝pug的依賴
npm install pug --save
此時會在package.json中加入pug的依賴
"dependencies": {
"express": "^4.16.2",
"pug": "^2.0.0-rc.4"
}
pug是nodejs默認(rèn)的視圖引擎课蔬,我們只要進(jìn)行簡單的配置即可使用
//說明的視圖的路徑是根路徑加上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}
];
//傳入了兩個參數(shù)
resp.render("test",{title:"hello pug",users:users});
});
上面的代碼設(shè)定了視圖引擎是pug二跋。它會自動在views目錄中找xx.pug來渲染战惊,/test這個路由使用的方法是resp.render()
這就表示會渲染給一個視圖"test.pug"文件,并且傳遞了title和users兩個數(shù)據(jù)扎即,接下來看看pug的編寫方法吞获,這個比較特殊,它是使用tab的縮進(jìn)來表示html內(nèi)容谚鄙,這里只是簡單的介紹各拷,大家如果有興趣可以花點時間去研究一下,視圖的研究無非就是從幾個點入手(如何傳數(shù)據(jù)闷营,如何展示成html烤黍,如何寫選擇,如何寫循環(huán),宏定義)蚊荣。下面的代碼我們定義了模板layout.pug。將其他模板繼承l(wèi)ayout.pug可以比較方便的實現(xiàn)基本模塊莫杈。
doctype html
html
head
title #{title}
body
h1 pug的基本例子
block content
hr
p(style="text-align:center") copyright
已上代碼定義了模板文件互例,注意block content
就是要在其他地方嵌套的內(nèi)容,下面看看test.pug的操作
extends layout
block content
ul
each u,i in users
li=i+"."+u.username+"---"+u.age
test.pug中繼承了layout筝闹,并且將block content中的內(nèi)容替換為一個無序列表媳叨,使用了each來遍歷users這個渲染視圖的參數(shù)。
關(guān)于pug大家可以自行學(xué)習(xí)关顷,也非常簡單糊秆。下面我們會介紹另外一個視圖模板引擎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的默認(rèn)引擎泌神,所以需要程序員注冊這個引擎
//注冊hbs引擎,說明引擎的后綴名是hbs舞虱,默認(rèn)的模板名稱是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>
需要注意的是兩個{{}}的,表示獲取傳過來的值焕刮,而三個{{{}}}表示引入具體的內(nèi)容舶沿,而且解析html。這里的{{{body}}}表示具體的模板內(nèi)容配并,我們也可以在app.js中設(shè)置不使用模板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創(chuàng)建項目
現(xiàn)在我們應(yīng)該已經(jīng)對express有了大致的了解畸冲,下面就可以使用express-generator來生成express項目了,這將會極大的簡化我們的開發(fā)操作,首先將express-generator安裝到全局中邑闲。
npm install express-generator -g
安裝完成之后使用
express 13_express
此時會完成express骨架的創(chuàng)建算行,首先看看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腳本,表示服務(wù)器的啟動文件不是app.js而是bin路徑中的www文件苫耸,我們可以將其修改為nodemon ./bin/www州邢,之后看看依賴包,首先body-parser用來解析http請求的褪子,可以非常輕松的解析json數(shù)據(jù)格式量淌;cookie-parse用來解析cookie請求,debug用來進(jìn)行調(diào)試嫌褪,jade就是模板引擎呀枢,可以將其修改為handlebars,morgan是日志組件笼痛,serve-favicon用來處理圖標(biāo)裙秋,此時這些包并沒有安裝到我們的項目中的,需要使用install進(jìn)行安裝
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());
//靜態(tài)文件的處理残吩,所有的靜態(tài)文件在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;
我們能夠找到一些熟悉的身影倘核,如模板引擎泣侮,我們可以注意到它已經(jīng)幫我們處理了靜態(tài)文件,在public文件夾中紧唱。而路由引擎是由兩個獨立的模塊來實現(xiàn)的而在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文件中專門增加自己的路由信息。至于其他模塊绰疤,大家在使用中自然而然就會了铜犬。關(guān)鍵是主要的思路,下面我們會給大家介紹session和表單的validate的模塊轻庆。
cookie-parser和body-parser模塊
首先看一個實例癣猾,通過表單提交一個post請求,首先新建一個項目余爆,導(dǎo)入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文件上陕,該文件中創(chuàng)建了一個表單
<!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傳遞了一個參數(shù)hello,表單是通過post的請求提交的拓春,接下來看看處理的代碼
app.post("/form",function (req,resp) {
resp.send(req.query.hello+","+req.query.username);
});
程序中通過req.query來獲取瀏覽器的get參數(shù)释簿,我們會發(fā)現(xiàn)req.query.username取不到任何值,這說明req.query僅僅只能獲取get的請求參數(shù)硼莽,那該如何獲取post的請求參數(shù)呢辕万?此時就需要使用body-parser的中間件來執(zhí)行。首先安裝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中的內(nèi)容就可以通過req.body來讀取醉途。通過這個實例大家應(yīng)該清楚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中的服務(wù)器跳轉(zhuǎn)。最后看看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的信息江咳。
在這一小節(jié)結(jié)束之前我們需要再次總結(jié)req獲取參數(shù)的三種方式:
1逢净、req.params.xx 這是獲取路徑中的參數(shù)
2、req.query.xx 這是獲取get請求的參數(shù)
3歼指、req.body.xx 這是通過body-parser來獲取form表單中的post請求爹土。
express的session
express同樣也支持session,需要express-session的支持东臀,首先通過npm安裝express-session
npm install express-session --save
在app.js中引入這個中間件并且創(chuàng)建session
var session = require("express-session");
app.use(session({
secret: 'a4f8071f-c873-4447-8ee2'
}));
secret是一個服務(wù)器端的簽名着饥,這個字符串可以隨便設(shè)定,之后通過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的路由來設(shè)定session宰掉,在sessionOk中來讀取session的值呵哨。session如果沒有設(shè)定特殊的cookie的值,關(guān)閉瀏覽器就失效轨奄。