Atwood's Law: Any application that can be written in JavaScript, will eventually be written in JavaScript.
任何可以用JavaScript实现的应用, 终将会用JavaScript来实现
在 Express 之前,我们是使用 Node.js 自带的 http 模块进行开发 一个基本的服务端;
// 引入http模块
const http = require("http")
// 创建服务
const app = http.createServer(function (request, response) {
if (request.url === '/favicon.ico') {
response.writeHead(404)
return response.end()
}
response.writeHead(200)
response.end('Hello World')
})
// 监听3000端口
app.listen(3000, function (err) {
if (err) return
console.log('服务启动')
})
上面, 我们就实现了简单的 一个 node 服务, 但是 我们遇到了一些问题;
- 如何实现路由?
- 如何获取请求的参数?
- 如何设置静态资源?
- 如何渲染模版?
- 如何....太多的困难了
会发现 我们一些常见的功能都需要自己去实现, 很麻烦不是? 所以 Express 应运而生, 其实
Express底层创建服务还是使用的http模块
提示
在很多面试的时候会问到如何让Node监听80端口? 在Linux系统中,普通用户是无法打开 1024 以下端口的, 当然包括了 80 的端口, 虽然 可以通过 root 来操作 Node, 别这样操作, 通过转发的来实现;
Express
官网上醒目的标题: Fast, unopinionated, minimalist web framework for Node.js
Web Applications | APIs | Performance | Frameworks |
|---|---|---|---|
| Express is a minimal and flexible Node.js web application framework that provides a robust(健壮的) set of features for web and mobile applications | With a myriad of HTTP utility methods and middleware at your disposal, creating a robust API is quick and easy. | Express provides a thin layer of fundamental web application features, without obscuring Node.js features that you know and love. | Many popular frameworks are based on Express. |
安装使用
yarn add express
const express = require("express")
const app = express()
app.get('/', function (req, res) {
res.statusCode = 200
res.send('Hello World')
})
app.listen(3000, function (err) {
if (err) return
console.log('服务启动')
})
有没有发现, 这个跟 上面的 app.listen 和 上面使用Node创建的 app.listen 是一样的? 你是对的, 你没想错, 我们看下源码:
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
其实就是 调用了 http 的 API;
创建路由
在 使用Node原生的时候, 我们需要根据不同的路由来响应不同的结果,是 通过判断 request.url来进行处理的,而且都在 http.createServer 回调函数中, 这样无疑会使得代码越来越臃肿;
Express 不仅帮我们解决了这个问题, 还在这个基础上增加了很多的功能;
- 请求方法: GET, PUT, POST等等
- 路由形式: 字符串, 字符串模式, 正则表达式
- 路由参数: 可以获取动态路由的参数
- 子路由: 可以实现子路由
请求方法
Express 支持以下路由方法:
checkoutcopydeletegetheadlockmergemkactivitymkcolmovem-searchnotifyoptionspatchpostpurgeputreportsearchsubscribetraceunlockunsubscribe1 GET
app.get('/', function (req, res) {
res.statusCode = 200
res.send('Hello World')
})
// 或者
app.route('/').get(function(req, res) {
res.send('Hello World'')
})
- POST
app.post('/', function (req, res) {
res.statusCode = 200
res.send('Hello World')
})
// 或者
app.route('/').post(function(req, res) {
res.send('Hello World'')
})
- GET + POST
app.get('/', function (req, res) {
res.statusCode = 200
res.send('Hello World')
})
app.post('/', function (req, res) {
res.statusCode = 200
res.send('Hello World')
})
// 或者
app.route('/')
.get(function(req, res) {
res.send('Hello World')
})
.post(function(req, res) {
res.send('Hello World')
})
- 全部的路由
app.all('/', function (req, res) {
res.statusCode = 200
res.send('Hello World')
})
路由形式
因为 Express 的路由匹配使用的是 path-to-regexp, 所以 路由形式: 字符串,字符串模式,正则表达式 其实都会转成 正则表达式
- 字符串
app.get('/about', (req, res) => {
res.send('about')
})
- 字符串模式
匹配:
abcd或者acd
app.get('/ab?cd', (req, res) => {
res.send('ab?cd')
})
匹配: b的个数 大于 1
app.get('/ab+cd', (req, res) => {
res.send('ab+cd')
})
匹配: abcd, abxcd, abRANDOMcd, ab123cd 等等, * 匹配任意字符
app.get('/ab*cd', (req, res) => {
res.send('ab*cd')
})
- 正则表达式
匹配: 路径包含
a
app.get(/a/, (req, res) => {
res.send('/a/')
})
匹配: fly结尾的
app.get(/.*fly$/, (req, res) => {
res.send('/.*fly$/')
})
路由参数
路由参数是 给URL路径 部分片段命名, 通过获取这些命名片段的值进行不同的处理; 比如
路由参数: /users/:userId/books/:bookId
路径: http://localhost:3000/users/chinazhang/books/12
那么
req.params: { "userId": "chinazhang", "bookId": "12" }
app.all("/users/:userId/books/:bookId", function (req, res) {
res.statusCode = 200
const userId = req.params.userId
const bookId = req.params.bookId
res.send('Hello World userId:' + userId + ' bookId:' + bookId)
})
// Hello World userId:chinazhang bookId:12
子路由(嵌套路由)
// 创建新的路由
const router = express.Router()
router.all('/', function(req, res) {
res.send('/birds')
})
// /birds/test
router.all('/test', function(req, res) {
res.send('/birds/test')
})
// 将新的路由 对应到 /birds 上
app.use('/birds', router)
中间件
中间件(Middleware )函数: 基本的结构function(req, res, next), 他可以访问 request, response, 以及有一个 next 函数; 贯穿整个请求响应周期;
一旦中间件定义在某个路由上, 那么当访问到该路由的时候, 就会触发和执行该中间件函数,然后 调用next函数继续执行后续的中间件或者是响应, 如果不调用next那么请求一直会挂起;
中间件可以执行以下任务:
- 执行任何代码
- 对请求request 和 响应 reponse 对象进行修改处理
- 可以提前结束 整个 请求响应周期
- 调用下一个中间件
例如: 先输出 我是中间件 然后 输出 后续处理
app.get('/', function(req, res, next){
console.log('我是中间件')
next()
}, function(req, res) {
console.log('后续处理')
res.send('Hello World')
})
应用级中间件
应用级中间件(Application-level middleware), 作用于整个 应用上, 也就是 *express()*创建的实例, 所有的路由 都会经过他;
通过使用 app.use() 或者 app.METHOD(), 这里的METHOD 指的是上面提到的全部的请求方法
app.use(): 没有挂载指定路径, 那么所有的请求都会经过他处理
const express = require('express')
const app = express()
app.use((req, res, next) => {
console.log('Time:', Date.now())
next()
})
app.METHOD(): 指定对应的路径
app.use('/user/:id', (req, res, next) => {
console.log('Request Type:', req.method)
next()
})
路由级中间件
路由级中间件(Router-level middleware): 其实跟 应用级中间件(Application-level middleware) 一样, 只不过他是绑定在 express.Router()的实例上,比如我们上那个子路由
const express = require("express")
const app = express()
// 创建新的路由
const router = express.Router()
// 中间件
router.use(function(req, res, next) {
console.log('子路由中间件')
next()
})
router.all('/', function(req, res) {
res.send('/birds')
})
// /birds/test
router.all('/test', function(req, res) {
res.send('/birds/test')
})
// 将新的路由 对应到 /birds 上
app.use('/birds', router)
app.listen(3000, function () {
// console.log('有没有发现, 这个跟 上面的http 是一样的?')
})
错误处理中间件
错误处理中间件(Error-handling middleware), 在 Node.js中,经常会听到一个 错误优先 这个概念, 也就是 在回调函数中, 错误在参数的一个 , 在 错误处理中间件中也是, 他拥有4个参数: err, req, res 和 next, 可以捕获错误
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})
app.get('/', (req, res) => {
const ifError = true
if (ifError) {
throw new Error('BROKEN')
} else {
res.send('success')
}
})
内置的中间件
-
express.static: 处理静态资源
-
express.json: 基于 body-parser 将请求信息转成JSON
-
express.urlencoded: urlencoded编解码
-
express.raw: 基于 body-parser 将请求信息转成Buffer
第三方插件
静态资源
在处理页面的时候, 我们避免不了处理静态资源, 在 Express 通过 express.static中间件 来处理静态资源
基本结构: express.static(root, [options]), root: 是静态资源的路径, option是配置选项
app.use(express.static('public'))
http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/images/bg.png
http://localhost:3000/hello.html
- 创建虚拟路径
app.use('/static', express.static('public'))
http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css
http://localhost:3000/static/js/app.js
http://localhost:3000/static/images/bg.png
http://localhost:3000/static/hello.html
模板渲染
在此之前,我们先了解几个概念:
- MVC: Model - Controller - View (传统的模板渲染), 服务端渲染成静态页面后返回给前端
- MVVM: UI框架, 数据驱动视图,减少DOM操作 (Vue, React 和 Angular)
- SPA: Single Page Application 单页面应用, 实现前后端分离, 路由由前端控制(Vue-Router, Reactr-Router)
- SSR: Server Rendering (服务端渲染), 为了体验(首屏响应),为了解决SPA的SEO问题
Express的模板渲染其实就是传统的 MVC 模式
-
Model(模型) - 用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法,对数据直接访问的权力,例如对数据库的访问
-
View(视图) - 能够实现数据有目的的显示
-
Controller(控制器) - 起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据 Model 上的改变。
现在我们看下 Express 如何实现 模板渲染
- 选择模板引擎(View Engine)
Express支持很多中模板引擎,
- 安装模板引擎 我们选择 pug 作为我们的模板引擎
yarn add pug
- 配置
Express默认模板引擎
app.set('view engine', 'pug')
- 创建模板
在根目录创建一个
views文件夹, 并且创建一个index.pug的模板文件。 title 和 author 都是 需要渲染的占位
html
head
title= title
body
h1= author
- 渲染模板
res.render(view, data),view从 views文件夹中找到指定的模板,data需要渲染的数据
app.get('/', function(req, res){
res.render('index', {
title: '模板引擎渲染页面',
author: '请叫我张先森'
})
})
完整代码:
const express = require("express")
const app = express()
app.set('view engine', 'pug')
app.get('/', function(req, res){
res.render('index', {
title: '模板引擎渲染页面',
author: '请叫我张先森'
})
})
app.listen(3000)
至此, Express 从安装到路由 再到模板渲染,已经讲完,可以在自己本地尝试,加深对 Express的理解和应用