node
Express 框架:
- 就是一个基于Node的web应用开发框架
- 安装:
npm i express
- 导入:
const express = require('express')
- 创建服务实例:
const app = express()
- 路由定义更简单:
- app.get
- app.post
- 获取请求参数更简单:
- get:req.query
- post:req.body
- express中不再使用
res.end
方法响应客户端,取而代之的是res.send
方法:- res.send方法内部也会调用res.end方法
- 可以自动设置HTTP状态码
- 可以自动检测响应内容类型并设置上去
- 自动设置编码格式
- 直接发送一个JSON对象
中间件(重要):
-
中间件就是在请求和响应过程中,夹的一层逻辑,可以理解为请求的拦截器
-
中间件有两部分组成:
- 定义中间件的方法:由Express提供
- 中间件处理函数:有程序员编写
-
中间件可以有多个,先定义的先进入(满足条件的情况下),可以调用next方法交给下一个中间件,直到遇到结束请求的中间件就over
-
接收所有请求:
app.use((req, res, next) => { // your code... next() })
-
接收以指定地址开头的请求
app.use('/xxxpath', (req, res, next) => { // your code... next() })
-
注意:如果不调用
next()
方法,是不会继续往下面走的 -
中间件应用:
- 路由拦截(保护)
- 网站维护公告
- 自定义404页面,可以通过res.status(404)方法来设置HTTP状态码
-
补充说明:res.send和next后,如果还有代码,是会执行的,所以我们一般都会在方法前加个return,阻止后续代码执行
- 注意:next方法是会等下一个中间件执行完成后才会继续往下执行
-
错误处理中间件:
-
错误处理中间件就是一个集中处理错误的地方:
app.use((err, req, res, next) => { // your code.. res.status(500).send(err.message) })
-
同步代码抛错:
throw new Error('错误消息')
-
异步代码抛错:
next(new Error('错误消息'))
,异步代码只能通过这种方式抛出去错误才能被错误处理中间件捕获到 -
异步函数错误捕获:
try { await User.find() } catch(err) { next(err) } // ------- await User.find().catch(err => next(err)) // 推荐用
- 补充说明:try … catch 只能捕获同步错误和异步函数错误,不能捕获其他异步API抛出的错误,比如:setTimeout、fs.readFile(‘some.file’, ‘utf8’, (err, data) => { if (err) { throw err } })
-
构建模块化路由:
-
路由是资源路径,比如:/index、/home、/home/index
-
路由器:是指定义和管理路由的对象
-
创建路由器:
const userRouter = express.Router()
-
模块化路由:将一类路由定义到一个模块中,方便管理维护
-
模块化路由范例(用户相关的路由、管理员相关的路由):
-
用户路由模块,routes/user.js
const express = require('express') const userRouter = express.Router() // /user/index userRouter.get('/index', (req, res) => { res.send('用户首页') }) // /user/reg userRouter.get('/reg', (req, res) => { res.send('用户注册页') }) module.exports = { userRouter }
-
管理员路由模块,routes/admin.js
const express = require('express') const adminRouter = express.Router() // /admin/index adminRouter.get('/index', (req, res) => { res.send('管理员首页') }) // /admin/data-manage adminRouter.get('/data-manage', (req, res) => { res.send('管理员数据管理页面') }) module.exports = { adminRouter }
-
入口文件,app.js:
const express = require('express') const app = express() const { userRouter } = require('./routes/user') const { adminRouter } = require('./routes/admin') // 当拦截到 /user 开头请求时,交给 userRouter 处理 app.use('/user', userRouter) // 当拦截到 /admin 开头请求时,交给 adminRouter 处理 app.use('/admin', adminRouter) app.listen(3000) console.log('服务启动成功')
-
Express中接收get请求参数:
// http://localhost:3000/index?name=zhangsan&age=18
app.get('/index', (req, res) => {
console.log(req.query) // { name: 'zhangsan', age: 18 }
res.send('ok')
})
注意:如果没有get参数,req.query 是一个空对象{}
,并不是null或undefined,所以在逻辑判断的时候要小心
接收post请求参数:
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))
// POST http://localhost:3000/add 请求体:name=zhangsan&age=18
app.post('/add', (req, res) => {
console.log(req.body) // { name: 'zhangsan', age: 18 }
res.send('ok')
})
- bodyParser.urlencoded方法的参数的extended是默认值是true,需要安装第三方模块
qs
,不然就跑不起来- 如果是false,那么就用系统模块querystring来解析请求参数
- 如果是true,那么就用第三方模块qs来解析请求参数
- 从功能性上来讲,qs比querystring要强大,但是我们一般用querystring足够了(也是官方推荐的),二般再说,所以可以根据实际项目需求而定
app.use方法传递函数调用语法解析:
// fn 是一个高阶函数(接收函数作为参数或者返回值是函数)
function fn(obj) {
// 这里实际就形成了闭包现象,返回了一个闭包函数
return function (req, res, next) {
// 根据条件逻辑判断输出呵呵&哈哈
if (obj.condition === 1) {
console.log('呵呵')
} else if (obj.condition === 2) {
console.log('哈哈')
}
next()
}
}
app.use(fn({ condition: 1 }))
路由参数(占位符)(RestFul风格的URL)(推荐)
app.get('/index/:name/:age', (req, res) => {
console.log(req.params) // { name: 'zs', age: 18 }
res.send('ok')
})
// 可以匹配如下路由:
// http://localhost:3000/index/zs/18
// http://localhost:3000/index/ls/28
// 不可匹配如下路由:
// http://localhost:3000/index/zs
// http://localhost:3000/index/zs/18/hehe
- 注意:参数个数不能多,不能少,否则匹配不上路由
静态资源访问:
const express = require('express')
const path = require('path')
const app = express()
// 用path.join拼接成绝对路径,指定静态资源根目录
// 所有请求都会优先走这个中间件,如果能找到匹配的静态资源,那么就返回,如果找不到,就调用next()交给下一个中间件去处理
app.use(express.static(path.join(__dirname, 'public')))
// 如果是上面这样挂载的中间件,那么访问静态资源的路径就是如下形式:
// http://localhost:3000/images/xxxx.png
// http://localhost:3000/css/xxx.css
// 这个xxx可以随便写,一般命名为public或static
app.use('/xxx' , express.static(path.join(__dirname, 'public'))) // 推荐用这种形式
// 如果是上面这样挂载的中间件,那么访问静态资源的路径就是如下形式:
// http://localhost:3000/xxx/images/xxx.png
// http://localhost:3000/xxx/css/xxx.css
// code...
app.listen(3000)
配置模板引擎:
-
我们这里使用
express-art-template
第三方模块,实际上内部也是基于art-template
来实现的,只不过为了更好的支持express
框架来做了一些额外的处理,语法还是art-template
的,所以安装时需要装两个 -
安装:
npm i express-art-template art-tempalte
-
上代码:
-
utils/art-template.js
const path = require('path') const artTemplate = require('express-art-template') module.exports = { artTemplate: (app) => { // 指定用artTemplate模板引擎去解析.html后缀的模板 // 理论上来讲,是可以同时存在多个模板引擎的,但是一般情况下,项目里面只会用一个模板引擎 app.engine('html', artTemplate) // 指定模板文件的根目录 app.set('views', path.join(__dirname, 'views')) // 指定模板文件的默认后缀为html app.set('view engine', 'html') } }
-
app.js:
const express = require('express') const app = express() const { artTemplate } = require('./utils/art-template') artTemplate(app) app.get('/index', (req, res) => { res.render('index', { name: 'zs', age: 18 }) // 1. 会自动去模板文件的根目录下查找名为index的模板文件 // 2. 会自动拼接模板默认后缀名 // 3. 会自动结合模板数据解析模板 // 4. 会自动将模板引擎解析出来的最终HTML响应给客户端,所以我们不需要再调用res.send和res.end方法了 })
-
index.html:
<div> 名字:{{ name }} <br> 年龄:{{ age }} </div>
-
app.locals对象:
-
app.locals对象中的键值对在所有的模板文件中都能拿到,以键值对的形式存在,一般我们用来存放公共的数据
-
语法:
app.locals.key = value
-
范例:
app.js
const express = require('express') const app = express() const { artTemplate } = require('./utils/art-template') artTemplate(app) const { User } = require('./model/User') app.get('/admin/home', async (req, res) => { // 在这个页面中需要获取所有的用户数据 const users = await User.find() res.render('home', { msg: '这是首页', users }) }) app.get('/admin/list', async (req, res) => { // 在这个页面中也需要获取所有的用户数据 const users = await User.find() // [ { name: 'zs', age: 18 }, { name: 'ls', age: 29 } ] res.render('list', { msg: '这是列表页', users }) }) app.listen(3000)
views/home.html
<div> <div>{{ msg }}</div> <ul> {{each users v }} <li>{{ v.name }}, {{ v.age }}</li> {{/each}} </ul> </div>
views/list.html
<div> <div>{{ msg }}</div> <ul> {{each users v }} <li>{{ v.name }}, {{ v.age }}</li> {{/each}} </ul> </div>
改造后的app.js
const express = require('express') const app = express() const { artTemplate } = require('./utils/art-template') artTemplate(app) const { User } = require('./model/User') app.use('/admin', async (req, res, next) => { app.locals.users = await User.find() next() }) app.get('/admin/home', (req, res) => { res.render('home', { msg: '这是首页' }) }) app.get('/admin/list', (req, res) => { res.render('list', { msg: '这是列表页' }) }) app.listen(3000)