1.hello world
1.1.安裝express
1.進(jìn)入到自己的項目目錄, 我這里是express-demo
cd express-demo
2.初始化項目村斟,生成package.json文件
npm init -y
3.安裝express
npm install express --save
1.2.簡單使用
在當(dāng)前項目下新建app.js文件
// app.js內(nèi)容
// 引入express
var express = require('express')
var app = express()
app.get('/hi', function (req, res, next){
res.send('hello world!!!')
})
app.listen(8090, function (error) {
console.log('listening on 8090')
})
1.3.自動監(jiān)聽文件邓梅,并且重啟
安裝,可以全局安裝也可以局部安裝
npm install -g nodemon
使用nodemon監(jiān)聽文件變化钱反,自動重啟服務(wù)
nodemon app.js
2.請求和響應(yīng)
2.1.請求相關(guān)
2.1.1.返回一個html頁面
app.get('/', function (req, res){
res.sendFile(path.resolve('./views/index.html'))
})
注意path模塊需要先引入
path模塊可以把一個路徑解析成一個對象面哥,對象中包含了很多屬性尚卫。
2.1.2.接收前臺get方式發(fā)送過來的數(shù)據(jù)
// get方式發(fā)送過來的數(shù)據(jù) 使用req.query接收
app.get('/getuser', function (req, res) {
console.log(req.query.userid)
})
// 完整代碼
// 引入express
var express = require('express')
var path = require('path')
var app = express()
var userArr = [
{"id": 1, "name": "xiaoqiang", "age": 18},
{"id": 2, "name": "xiaoli", "age": 19},
{"id": 3, "name": "xiaowang", "age": 20},
{"id": 4, "name": "xiaozhang", "age": 21}
]
app.get('/', function (req, res){
res.sendFile(path.resolve('./views/index.html'))
})
app.get('/user', function (req, res) {
res.sendFile(path.resolve('./views/login.html'))
})
app.get('/getuser', function (req, res) {
res.send(userArr.filter(function (item) {
console.log(item.id, req.query.userid)
return item.id == req.query.userid
}))
})
app.listen(8090, function (error) {
console.log('listening on 8090')
})
2.1.3.接收前臺post方式發(fā)送過來的數(shù)據(jù)
接收post數(shù)據(jù)刹泄,我們可以使用一個叫做body-parser的模塊來幫我們完成
1.第一步特石,先安裝這個模塊
npm install body-parser --save
2.第二步鳖链,引入這個模塊,并且作為插件使用
var bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({extend: false}))
3.第三步狂秦,獲取數(shù)據(jù)
app.post('/login', function (req, res) {
res.send(req.body.user)
})
2.1.4.文件上傳
文件上傳需要用到multer插件裂问,先安裝multer
npm install multer --save
接下來需要引入multer
var multer = require('multer')
配置multer
var upload = multer({dest: './upload'})
dest 表示 設(shè)置上傳文件目錄
會在當(dāng)前目錄下自動生成一個upload文件夾愕秫,用來存放上傳的文件
接下來我們在使用這個multer的時候不能用app.use戴甩,因為如果是用app.use的方式使用的話甜孤,會多次觸發(fā)缴川,而我們只希望在對應(yīng)的請求處理的時候進(jìn)行使用把夸。
- 單文件上傳
app.post('/upload', upload.single('fl'), function (req, res) {
res.send(req.file)
})
處理文件上傳請求
f1是指的文件上傳的input的name
- 多文件上傳
app.post('/upload', upload.array('fl', 3), function (req, res) {
res.send(req.files)
})
- 自定義文件路徑和文件名
node里面一般帶有sync的都象征著是同步方法
var fs = require('fs')
// 檢查目錄是否存在恋日,不存在就創(chuàng)建岂膳,如果存在谈截,就直接使用
var createFolder = function(folder) {
try {
//accessSync如果沒有檢測到涧偷,就會報錯
//如果報錯了嫂丙,就會被catchh抓獲
fs.accessSync(folder)
} catch (e) {
fs.mkdirSync(folder)
//異步方法通常都是需要用到回調(diào)函數(shù)的跟啤,所以這里我們要用同步方法,
//也就是要加上sync
}
}
//設(shè)置好路徑和文件名
var uploadFolder = './upload'
// 先創(chuàng)建好上傳目錄
createFolder(uploadFolder)
//去自定義文件上傳后的路徑和自定義文件名字
var storage = multer.diskStorage({
//cb就是callback的縮寫袄简,回調(diào)函數(shù)
destination: function (req, file, cb) {
//下面這行代碼的意思:有文件就按照uploadFolder進(jìn)行上傳就行了
cb(null, uploadFolder)
},
filename: function (req, file, cb) {
//cb(null,file.originalname) --->這樣傳的話绿语,文件原來是什么名字吕粹,上傳之后就還是什么名字
cb(null, file.fieldname + '-' + new Date().getTime() + path.extname(file.originalname))
}
})
//第一個storage就是storage定義匹耕,第二個storage是我們定義的上面的那幾行代碼
var upload = multer({storage: storage})
app.post('/upload', upload.array('fl', 3), function (req, res) {
res.send(req.files)
})
2.2.響應(yīng)相關(guān)
2.2.1.send方法
send方法可以返回多種類型數(shù)據(jù)
// 返回一個對象
res.send({"name":"老張", age: 48})
// 返回數(shù)組
// res.send([1, 2, 3])
// 報錯
// res.write({"name": "xiaoqiang"})
// res.end()
2.2.2.sendStatus
返回狀態(tài)碼
res.sendHeader(404)
如果沒有這個方法稳其,你會這樣寫:
res.writeHead(404, {'Content-Type': 'text/plain'})
res.write("not found!!!!")
res.end()
很明顯第一種寫法更簡潔既鞠。
2.2.3.redirect
redirect用于重定向
app.get('/re', function (req, res) {
res.redirect('http://nodeing.com')
})
如果不使用redirect嘱蛋,而使用原生的語法五续,需要這樣寫:
res.setHeader('location', 'http://nodeing.com')
res.writeHead(301)
res.end()
- 301 永久重定向 瀏覽器會記住
- a.com b.com
- a 瀏覽器不會請求 a 了
- 直接去跳到 b 了
- 通過看status code浑槽,可以發(fā)現(xiàn)后面寫著from cache
- 302 臨時重定向 瀏覽器不記憶
- a.com b.com
- a.com 還會請求 a
- a 告訴瀏覽器你往 b
3.路由
路由到底是什么呢?不管官方定義到底是什么返帕,咱通俗的說就是根據(jù)不同的url,執(zhí)行不同的代碼篙挽,類似于編程語言中的分支結(jié)構(gòu)
3.1.express規(guī)劃路由
稍微復(fù)雜點的應(yīng)用荆萤,通常都是分模塊進(jìn)行的
我們從中挑選幾個模塊進(jìn)行路由規(guī)劃,在我們的后臺模塊里面铣卡,可以實現(xiàn)用戶的管理,課程的管理煮落,友情鏈接管理等敞峭,我們的訪問地址可能是這樣的
// 1.用戶管理
// 用戶列表
http://localhost:8090/admin/user/list
// 添加用戶
http://localhost:8090/admin/user/add
// 刪除用戶
http://localhost:8090/admin/user/delete
// 編輯用戶
http://localhost:8090/admin/user/edit
// 2.課程管理
// 課程列表
http://localhost:8090/admin/course/list
// 添加用戶
http://localhost:8090/admin/course/add
// 刪除用戶
http://localhost:8090/admin/course/delete
// 編輯用戶
http://localhost:8090/admin/course/edit
...
在沒有拆分路由的情況下,我們需要在app.js里面寫這些代碼
/**
* 用戶管理模塊
*/
app.get('/admin/user/list', function (req, res) {
res.send('用戶列表')
})
app.get('/admin/user/add', function (req, res) {
res.send('添加用戶')
})
app.get('/admin/user/delete', function (req, res) {
res.send('刪除用戶')
})
app.get('/admin/user/edit', function (req, res) {
res.send('更新用戶')
})
/**
* 課程模塊
*/
app.get('/admin/course/list', function (req, res) {
res.send('課程列表')
})
app.get('/admin/course/add', function (req, res) {
res.send('添加課程')
})
app.get('/admin/course/delete', function (req, res) {
res.send('刪除課程')
})
app.get('/admin/course/edit', function (req, res) {
res.send('更新課程')
})
當(dāng)上面的代碼都寫到app.js中蝉仇,代碼會顯得非常臃腫旋讹,最佳的實踐是把這些模塊拆分出去殖蚕,express中提供了拆分的方法
第一步,我們在項目根目錄下面沉迹,新建一個router目錄睦疫,在這個目錄下面按模塊名字分別創(chuàng)建user.js和course.js
第二步,在user.js文件中鞭呕,創(chuàng)建router蛤育,并導(dǎo)出
var express = require('express')
var router = express.Router()
// ... 中間寫對應(yīng)的路由方法
module.exports = router
第三步,把對應(yīng)的路由方法添加到user.js中
var express = require('express')
var router = express.Router()
router.get('/admin/user/list', function (req, res) {
res.send('用戶列表')
})
router.get('/admin/user/add', function (req, res) {
res.send('添加用戶')
})
router.get('/admin/user/delete', function (req, res) {
res.send('刪除用戶')
})
router.get('/admin/user/edit', function (req, res) {
res.send('更新用戶')
})
module.exports = router
經(jīng)過前面步驟葫松,我們完成了user模塊路由拆分
接下來瓦糕,我們可以按照這種方式,把course模塊拆分出來
// course.js文件代碼
var express = require('express')
var router = express.Router()
router.get('/admin/course/list', function (req, res) {
res.send('課程列表')
})
router.get('/admin/course/add', function (req, res) {
res.send('添加課程')
})
router.get('/admin/course/delete', function (req, res) {
res.send('刪除課程')
})
router.get('/admin/course/edit', function (req, res) {
res.send('更新課程')
})
module.exports = router
當(dāng)我們各個路由模塊都拆分完成后腋么,如何使用這些模塊呢咕娄?
在app.js中,我們需要引入創(chuàng)建好的路由模塊
var userRouter = require('./router/user')
var courseRouter = require('./router/course')
接下來党晋,掛載到express上
app.use('/', userRouter)
app.use('/', courseRouter)
經(jīng)過以上步驟谭胚,我們已經(jīng)把路由模塊完全拆分出去了
3.2.對路由模塊進(jìn)行多級拆分
前面我們已經(jīng)把模塊劃分出來了,我們在寫路由方法的時候是這樣的:
router.get('/admin/user/list', function (req, res) {
res.send('用戶列表')
})
從代碼中我們可以看成未玻,寫路徑的時候會寫一長串灾而,/admin/user/list,這樣寫比較麻煩扳剿,同時旁趟,我們更希望大模塊直接有更好的劃分,例如庇绽,我們的系統(tǒng)總體上劃分為前臺模塊和后臺模塊锡搜,那我們的router文件夾中應(yīng)該再分home和admin兩個文件夾
admin文件夾下面放的都是關(guān)于后臺路由的模塊,home目錄下面放的都是前臺路由的模塊瞧掺,這個時候耕餐,我們需要對路由做進(jìn)一步拆分,以拆分admin為例:
第一步:在admin/index.js下面 引入其他admin下面的模塊,把其他admin下面的模塊都掛在index.js這個模塊上然后導(dǎo)出
var express = require('express')
var router = express.Router()
var course = require('./course')
var user = require('./user')
// 掛載user模塊
router.use('/', user)
// 掛載course模塊
router.use('/', course)
module.exports = router
第二步:在app.js中引入admin模塊
//這里的index.js可以不寫.js辟狈,瀏覽器加載時會自動加上后綴
var adminRouter = require('./router/admin/index')
第三步:把adminRouter掛到app對象上
//如果我們請求/admin的話肠缔,會自動去找到adminRouter模塊
app.use('/admin', adminRouter)
第四步:需要注意,user.js模塊中的請求路徑需要改變哼转,例如:原來的"/admin/user/list"這種寫法明未,需要改成這種"/user/list"
3.3.動態(tài)路由
動態(tài)路由就是路由是動態(tài)不固定的,例如:
http;//localhost:8090/user/1
http;//localhost:8090/user/2
http;//localhost:8090/user/3
上面的幾個url中壹蔓,都是去訪問某個user喊式,前面部分(http;//localhost:8090/user)是相同的擦俐,不同的就是后面的部分
在后臺我們怎么去監(jiān)聽這種形式的url呢伐弹?我們可以弄一個變量來匹配這些不同的部分,例如:
app.get('/user/:id', function (req, res) {
console.log(req.params)
})
這其中的id就存儲了url中變化的部分
/user/1 id: 1
/user/2 id: 2
/user/3 id: 3
可以通過req.params.id打印出每次請求的動態(tài)部分(動態(tài)參數(shù))
4.靜態(tài)文件
4.1.普通處理靜態(tài)文件的方法
在./views/index.html文件中去引入另一個css文件index.css,index.css文件放在public/css目錄下亲雪。
index.html文件中的內(nèi)容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#wrap {
width: 300px;
margin: 100px auto;
line-height: 150px;
height: 150px;
text-align: center;
background-color: green;
}
#wrap a {
color: white;
text-decoration: none;
}
</style>
<link rel="stylesheet" href="/public/css/index.css">
</head>
<body>
<div id="wrap">
<a href="/login">登錄 | </a>
<a href="/user">用戶中心</a>
</div>
</body>
</html>
根據(jù)請求渲染出index.html文件
app.get('/', function (req, res) {
res.sendFile('./index.html')
})
當(dāng)我們方法 '/'這個路徑的時候,能把index.html頁面加載出來行疏,但是沒辦法把css文件加載出來
為了解決這個問題匆光,我們還需要單獨去寫一個路由去返回css文件
app.get('/public/css/index.css', function (req, res) {
res.sendFile(path.resolve('./public/css/index.css'))
})
4.2.express中處理靜態(tài)文件的插件
express中提供了處理靜態(tài)文件的插件,這里的靜態(tài)文件就是我們項目中需要用到的img酿联、css终息、js等資源,只需要簡單的配置就可以實現(xiàn)對靜態(tài)文件的處理,步驟如下:
第一步贞让,在app中掛載插件
app.use(express.static('./public'))
第二步周崭,使用靜態(tài)文件,在index.html文件中引入css,路徑需要修改一下
<link rel="stylesheet" href="/css/index.css">
5.ejs模版
5.1.什么是模版引擎喳张?
為了使用戶界面與業(yè)務(wù)數(shù)據(jù)(內(nèi)容)分離而產(chǎn)生的续镇,它可以生成特定格式的文檔,用于網(wǎng)站的模板引擎就會生成一個標(biāo)準(zhǔn)的HTML文檔销部。
在后端開發(fā)中摸航,處理數(shù)據(jù)的代碼和展示數(shù)據(jù)的代碼是分離的,這就是前面說的舅桩,用戶界面和業(yè)務(wù)數(shù)據(jù)內(nèi)容分離酱虎,但是真的展現(xiàn)到前端給用戶看到的界面都是數(shù)據(jù)和界面融合在一起的,模版引擎的作用就是把html文件和后端的數(shù)據(jù)柔和在一起生成一個html文件返回給前端展示擂涛,這種方式又叫做服務(wù)端渲染读串。
模板引擎在服務(wù)端所傳遞的變量,只會在前端的html中執(zhí)行撒妈,并且只認(rèn)識自己的特定符號恢暖,js代碼不會執(zhí)行模板引擎,js會直接放給瀏覽器執(zhí)行狰右。
5.2.在express中使用ejs模版引擎
第一步杰捂,安裝ejs
npm install ejs --save
第二步,在app.js中引入ejs
var ejs = require('ejs')
第三步棋蚌,設(shè)置express的模版文件夾琼娘,app.set方法,表示設(shè)置某個屬性名的value附鸽,例如:設(shè)置express的views,views指express中模版文件的路徑瞒瘸,路徑的值為第二個參數(shù)給的值
app.set('views', path.join(__dirname, 'views'))
第四步坷备,告訴express使用ejs來作為模板引擎,并且設(shè)置模板文件后綴
app.engine('html', ejs.__express)
第五步情臭,注冊模板引擎
app.set('view engine', 'html')
第六步省撑,ejs模板引擎初體驗
1.在"/"路由中赌蔑,渲染"index.html"文件,并帶參數(shù)
app.get('/', function (req, res){
// res.sendFile(path.resolve('./views/index.html'))
res.render('index.html', {title: '螺釘課堂>癸M薰摺!'})
})
2.在‘index.html’文件中去使用數(shù)據(jù)
<h1><%= title %></h1>
5.3.ejs的常用語法
1.基本語法肥败,后臺數(shù)據(jù)是融和在html模板中的趾浅,在html模板中,通過自定義標(biāo)簽的形式來區(qū)分到底是ejs的標(biāo)簽還是html的標(biāo)簽 例如:
<%= title %>
常用的標(biāo)簽:
1馒稍、<% if|for %> 這種叫做腳本標(biāo)簽皿哨,用于寫流程控制
2、<%= 變量 %> 這種標(biāo)簽的作用是把數(shù)據(jù)輸出到html
3纽谒、<%- %>這種標(biāo)簽的作用和<%= %>相同证膨,區(qū)別是這種標(biāo)簽可以解析html,<%= %>這種標(biāo)簽會把html標(biāo)簽給轉(zhuǎn)義了
2.流程控制語句
- if 語句
// 1.后臺傳入一個 isLogin字段
app.get('/', function (req, res){
// res.sendFile(path.resolve('./views/index.html'))
res.render('index.html', {title: '螺釘課堂9那Q肜铡!', isLogin: false})
})
// 2.在模板中使用這個isLogin字段來做判斷
<% if (isLogin) { %>
<div id="wrap">
<a href="/login">歡迎admin澳化,登錄4薏健!肆捕!</a>
<a href="/user">用戶中心</a>
</div>
<% } else { %>
<div id="wrap">
<a href="/login">登錄 | </a>
<a href="/user">用戶中心</a>
</div>
<% } %>
- for循環(huán)渲染
// 1.在后臺傳入一個數(shù)組
app.get('/', function (req, res){
// res.sendFile(path.resolve('./views/index.html'))
var userList = [
{name: '張飛', age: 29},
{name: '關(guān)羽', age: 30},
{name: '劉備', age: 31},
]
res.render('index.html', {title: '螺釘課堂K⒔!慎陵!', isLogin: false, userList: userList})
})
// 2.在模板中循環(huán)出這個數(shù)組
<ul>
<% for (var i = 0; i < userList.length; i++) {%>
<li><%= userList[i].name %> ----> <%= userList[i].age %></li>
<% } %>
</ul>
// 3.也可以使用forEach方法來循環(huán)
<ul>
<% userList.forEach(function (item){%>
<li><%= item.name %> ----> <%= item.age%></li>
<% }) %>
</ul>
5.中間件
中間件可以理解為過濾器眼虱,我們通過一個自定義的body中間件來闡述到底什么是中間件?
中間件本質(zhì)上是一個函數(shù)
中間件都會有一個next參數(shù)席纽,讓過濾器能夠一層一層往下執(zhí)行捏悬。
中間件需要在路由之前執(zhí)行。
中間件配置必須放在router掛載的前面
在項目根目錄下面創(chuàng)建一個libs文件夾润梯,然后創(chuàng)建body.js文件
body.js內(nèi)容
const queryString = require('querystring')
function body(req, res, next) {
let arr = []
req.on('data', (chunk, err) => {
if (!err) {
arr.push(chunk)
}
})
req.on('end', (err) => {
//arr沒處理之前是一個二進(jìn)制對象
//用Buffer.concat把arr數(shù)組里的二進(jìn)制對象都給連接起來
//.toString()把前面整個轉(zhuǎn)換成字符串
//queryString.parse把字符串解析成對象
req.body = queryString.parse(Buffer.concat(arr).toString())
next()
})
}
module.exports = body
在express中使用body中間件
const express = require('express')
const app = express()
const body = require('./libs/body.js')
app.use(body)
app.post('/user', function(req, res) {
console.log(req.body)
})
app.listen(4001)
中間件:處理請求的过牙,本質(zhì)就是個函數(shù)
在 Express 中,對中間件有幾種分類
當(dāng)請求進(jìn)來纺铭,會從第一個中間件開始進(jìn)行匹配
如果匹配寇钉,則進(jìn)來
如果請求進(jìn)入中間件之后,沒有調(diào)用 next 則代碼會停在當(dāng)前中間件
如果調(diào)用了 next 則繼續(xù)向后找到第一個匹配的中間件
如果不匹配舶赔,則繼續(xù)判斷匹配下一個中間件
不關(guān)心請求路徑和請求方法的中間件
也就是說任何請求都會進(jìn)入這個中間件
中間件本身是一個方法扫倡,該方法接收三個參數(shù):
Request 請求對象
Response 響應(yīng)對象
next 下一個中間件
當(dāng)一個請求進(jìn)入一個中間件之后,如果不調(diào)用 next 則會停留在當(dāng)前中間件
所以 next 是一個方法竟纳,用來調(diào)用下一個中間件的
調(diào)用 next 方法也是要匹配的(不是調(diào)用緊挨著的那個)
如果調(diào)用next方法的時候撵溃,傳err參數(shù)疚鲤,則會將錯誤統(tǒng)一匹配給錯誤處理中間件,也就是帶有四個參數(shù)的應(yīng)用程序級別的中間件缘挑。
app.use(function (err, req, res, next) {
res.status(500).send(err.message)
})
應(yīng)用程序級別中間件
萬能匹配(不關(guān)心任何請求路徑和請求方法)
app.use( function (req, res , next){
console.log('Time:',Date.now())
next()
})
只要是以’/xxx/‘開頭的
app.use ('/a',function (req,res,next){
console.log('Time:',Date.now())
next()
})
路徑級別中間件
get:
app.get ('/',function (req,res){
res.end('Hello World!')
})
post:
app.post('/',function (req,res){
res.end('Got a POST request')
})
put:
app.put( '/user',function (req,res){
res.send ('Got a PUT request at /user')
})
express解決跨域問題
方式一:
不用中間件的話可以這樣寫
const express = require('express')
const app = express()
app.use((req, res, next) => {
// 設(shè)置是否運行客戶端設(shè)置 withCredentials
// 即在不同域名下發(fā)出的請求也可以攜帶 cookie
res.header("Access-Control-Allow-Credentials",true)
// 第二個參數(shù)表示允許跨域的域名集歇,* 代表所有域名
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, OPTIONS') // 允許的 http 請求的方法
// 允許前臺獲得的除 Cache-Control、Content-Language语淘、Content-Type诲宇、Expires、Last-Modified亏娜、Pragma 這幾張基本響應(yīng)頭之外的響應(yīng)頭
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With')
if (req.method == 'OPTIONS') {
res.sendStatus(200)
} else {
next()
}
})
方式二:
使用CORS,和其他中間件的用法一樣焕窝,app.use()即可:
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors())
app.get('/products/:id', function (req, res, next) {
res.json({msg: 'This is CORS-enabled for all origins!'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
如果要單獨為某個接口實現(xiàn)允許跨域請求,在回調(diào)函數(shù)之前先用cors()方法進(jìn)行跨域處理即可:
var express = require('express')
var cors = require('cors')
var app = express()
app.get('/products/:id', cors(), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for a Single Route'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
還可以自己手動配置:
var express = require('express')
var cors = require('cors')
var app = express()
var corsOptions = {
origin: 'http://example.com',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}
app.get('/products/:id', cors(corsOptions), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for only example.com.'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})