基于Express实现Node服务端应用

417 阅读8分钟

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 服务, 但是 我们遇到了一些问题;

  1. 如何实现路由?
  2. 如何获取请求的参数?
  3. 如何设置静态资源?
  4. 如何渲染模版?
  5. 如何....太多的困难了 会发现 我们一些常见的功能都需要自己去实现, 很麻烦不是? 所以 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 applicationsWith 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 不仅帮我们解决了这个问题, 还在这个基础上增加了很多的功能;

  1. 请求方法: GET, PUT, POST等等
  2. 路由形式: 字符串, 字符串模式, 正则表达式
  3. 路由参数: 可以获取动态路由的参数
  4. 子路由: 可以实现子路由

请求方法

Express 支持以下路由方法:

  • checkout
  • copy
  • delete
  • get
  • head
  • lock
  • merge
  • mkactivity
  • mkcol
  • move
  • m-search
  • notify
  • options
  • patch
  • post
  • purge
  • put
  • report
  • search
  • subscribe
  • trace
  • unlock
  • unsubscribe 1 GET
app.get('/', function (req, res) {
  res.statusCode = 200
  res.send('Hello World')
})
// 或者 
app.route('/').get(function(req, res) {
 res.send('Hello World'')
})
  1. POST
app.post('/', function (req, res) {
  res.statusCode = 200
  res.send('Hello World')
})
// 或者 
app.route('/').post(function(req, res) {
 res.send('Hello World'')
})
  1. 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')
  })
  1. 全部的路由
app.all('/', function (req, res) {
  res.statusCode = 200
  res.send('Hello World')
})

路由形式

因为 Express 的路由匹配使用的是 path-to-regexp, 所以 路由形式: 字符串,字符串模式,正则表达式 其实都会转成 正则表达式

  1. 字符串
app.get('/about', (req, res) => {
  res.send('about')
})
  1. 字符串模式 匹配: abcd 或者 acd
app.get('/ab?cd', (req, res) => {
  res.send('ab?cd')
})

匹配: b的个数 大于 1

app.get('/ab+cd', (req, res) => {
  res.send('ab+cd')
})

匹配: abcdabxcdabRANDOMcdab123cd 等等, * 匹配任意字符

app.get('/ab*cd', (req, res) => {
  res.send('ab*cd')
})
  1. 正则表达式 匹配: 路径包含 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')  
})

image.png

应用级中间件

应用级中间件(Application-level middleware), 作用于整个 应用上, 也就是 *express()*创建的实例, 所有的路由 都会经过他;

通过使用 app.use() 或者 app.METHOD(), 这里的METHOD 指的是上面提到的全部的请求方法

  1. app.use(): 没有挂载指定路径, 那么所有的请求都会经过他处理
const express = require('express')
const app = express()

app.use((req, res, next) => {
  console.log('Time:', Date.now())
  next()
})
  1. 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')
  }
})

image.png image.png

内置的中间件

第三方插件

github.com/senchalabs/…

静态资源

在处理页面的时候, 我们避免不了处理静态资源, 在 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
  1. 创建虚拟路径
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

模板渲染

在此之前,我们先了解几个概念:

  1. MVC: Model - Controller - View (传统的模板渲染), 服务端渲染成静态页面后返回给前端
  2. MVVM: UI框架, 数据驱动视图,减少DOM操作 (Vue, React 和 Angular)
  3. SPA: Single Page Application 单页面应用, 实现前后端分离, 路由由前端控制(Vue-Router, Reactr-Router)
  4. SSR: Server Rendering (服务端渲染), 为了体验(首屏响应),为了解决SPA的SEO问题

Express的模板渲染其实就是传统的 MVC 模式

  • Model(模型)  - 用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法,对数据直接访问的权力,例如对数据库的访问

  • View(视图)  - 能够实现数据有目的的显示

  • Controller(控制器)  - 起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据 Model 上的改变。

1200px-ModelViewControllerDiagram2.svg_.png

现在我们看下 Express 如何实现 模板渲染

  1. 选择模板引擎(View Engine) Express 支持很多中模板引擎

image.png

  1. 安装模板引擎 我们选择 pug 作为我们的模板引擎
yarn add pug
  1. 配置Express默认模板引擎
app.set('view engine', 'pug')
  1. 创建模板 在根目录创建一个views文件夹, 并且创建一个 index.pug 的模板文件。 title 和 author 都是 需要渲染的占位
html 
  head 
    title= title 
  body 
    h1= author
  1. 渲染模板 res.render(view, data), view 从 views文件夹中找到指定的模板, data 需要渲染的数据
app.get('/', function(req, res){
  res.render('index', {
    title: '模板引擎渲染页面',
    author: '请叫我张先森'
  })
})

image.png

完整代码:

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的理解和应用