第8章 使用 Express 快速进行 Web 开发

学习目标

  • 掌握使用 Express 处理静态资源
  • 理解路由概念
  • 掌握 Express 路由的基本使用
  • 理解模板引擎概念
  • 掌握模板引擎的基本使用
  • 理解 Express 中间件执行模型
  • 案例:Express 重写留言本案例
  • 案例:基于文件的增删改查
    • JSON 数据

原生的 http 模块在某些方面表现不足以应对我们的开发需求,所以我们就需要使用框架来加快我们的开发效率,框架的目的就是提高效率,让我们的代码更统一。
在 Node 中,有很多 Web 开发框架,我们这里以学习 Express 为主。

Express 介绍

  • Express 是一个基于 Node.js 平台,快速、开放、极简的 web 开发框架。

起步

安装

参考文档:http://expressjs.com/en/starter/installing.html

  1. # 创建并切换到 myapp 目录
  2. mkdir myapp
  3. cd myapp
  4. # 初始化 package.json 文件
  5. npm init -y
  6. # 安装 express 到项目中
  7. npm i express

Hello World

参考文档:http://expressjs.com/en/starter/hello-world.html

  1. // 0. 加载 Express
  2. const express = require('express')
  3. // 1. 调用 express() 得到一个 app
  4. // 类似于 http.createServer()
  5. const app = express()
  6. // 2. 设置请求对应的处理函数
  7. // 当客户端以 GET 方法请求 / 的时候就会调用第二个参数:请求处理函数
  8. app.get('/', (req, res) => {
  9. res.send('hello world')
  10. })
  11. // 3. 监听端口号,启动 Web 服务
  12. app.listen(3000, () => console.log('app listening on port 3000!'))

基本路由

参考文档:http://expressjs.com/en/starter/basic-routing.html

路由(Routing)是由一个 URI(或者叫路径标识)和一个特定的 HTTP 方法(GET、POST 等)组成的,涉及到应用如何处理响应客户端请求。

每一个路由都可以有一个或者多个处理器函数,当匹配到路由时,这个/些函数将被执行。

路由的定义的结构如下:

  1. app.METHOD(PATH, HANDLER)

其中:

  • app 是 express 实例
  • METHOD 是一个 HTTP 请求方法
  • PATH 是服务端路径(定位标识)
  • HANDLER 是当路由匹配到时需要执行的处理函数

下面是一些基本示例。

Respond with Hello World! on the homepage:

  1. // 当你以 GET 方法请求 / 的时候,执行对应的处理函数
  2. app.get('/', function (req, res) {
  3. res.send('Hello World!')
  4. })

Respond to POST request on the root route (/), the application’s home page:

  1. // 当你以 POST 方法请求 / 的时候,指定对应的处理函数
  2. app.post('/', function (req, res) {
  3. res.send('Got a POST request')
  4. })

Respond to a PUT request to the /user route:

  1. app.put('/user', function (req, res) {
  2. res.send('Got a PUT request at /user')
  3. })

Respond to a DELETE request to the /user route:

  1. app.delete('/user', function (req, res) {
  2. res.send('Got a DELETE request at /user')
  3. })

For more details about routing, see the routing guide.

处理静态资源

参考文档:http://expressjs.com/en/starter/static-files.html

  1. // 开放 public 目录中的资源
  2. // 不需要访问前缀
  3. app.use(express.static('public'))
  4. // 开放 files 目录资源,同上
  5. app.use(express.static('files'))
  6. // 开放 public 目录,限制访问前缀
  7. app.use('/public', express.static('public'))
  8. // 开放 public 目录资源,限制访问前缀
  9. app.use('/static', express.static('public'))
  10. // 开放 publi 目录,限制访问前缀
  11. // path.join(__dirname, 'public') 会得到一个动态的绝对路径
  12. app.use('/static', express.static(path.join(__dirname, 'public')))

使用模板引擎

参考文档:

我们可以使用模板引擎处理服务端渲染,但是 Express 为了保持其极简灵活的特性并没有提供类似的功能。

同样的,Express 也是开放的,它支持开发人员根据自己的需求将模板引擎和 Express 结合实现服务端渲染的能力。

配置使用 art-template 模板引擎

参考文档:

这里我们以 art-template 模板引擎为例演示如何和 Express 结合使用。

安装:

  1. npm install art-template express-art-template

配置:

  1. // 第一个参数用来配置视图的后缀名,这里是 art ,则你存储在 views 目录中的模板文件必须是 xxx.art
  2. // app.engine('art', require('express-art-template'))
  3. // 这里我把 art 改为 html
  4. app.engine('html', require('express-art-template'))

使用示例:

  1. app.get('/', function (req, res) {
  2. // render 方法默认会去项目的 views 目录中查找 index.html 文件
  3. // render 方法的本质就是将读取文件和模板引擎渲染这件事儿给封装起来了
  4. res.render('index.html', {
  5. title: 'hello world'
  6. })
  7. })

如果希望修改默认的 views 视图渲染存储目录,可以:

  1. // 第一个参数 views 是一个特定标识,不能乱写
  2. // 第二个参数给定一个目录路径作为默认的视图查找目录
  3. app.set('views', 目录路径)

其它常见模板引擎

JavaScript 模板引擎有很多,并且他们的功能都大抵相同,但是不同的模板引擎也各有自己的特色。

大部分 JavaScript 模板引擎都可以在 Node 中使用,下面是一些常见的模板引擎。

  • ejs
  • handlebars
  • jade
    • 后改名为 pug
  • nunjucks

解析表单 post 请求体

参考文档:

在 Express 中没有内置获取表单 POST 请求体的 API,这里我们需要使用一个第三方包:body-parser

安装:

  1. npm install --save body-parser

配置:

  1. var express = require('express')
  2. // 0. 引包
  3. var bodyParser = require('body-parser')
  4. var app = express()
  5. // 配置 body-parser
  6. // 只要加入这个配置,则在 req 请求对象上会多出来一个属性:body
  7. // 也就是说你就可以直接通过 req.body 来获取表单 POST 请求体数据了
  8. // parse application/x-www-form-urlencoded
  9. app.use(bodyParser.urlencoded({ extended: false }))
  10. // parse application/json
  11. app.use(bodyParser.json())

使用:

  1. app.use(function (req, res) {
  2. res.setHeader('Content-Type', 'text/plain')
  3. res.write('you posted:\n')
  4. // 可以通过 req.body 来获取表单 POST 请求体数据
  5. res.end(JSON.stringify(req.body, null, 2))
  6. })

使用 Session

参考文档:https://github.com/expressjs/session

安装:

  1. npm install express-session

配置:

  1. // 该插件会为 req 请求对象添加一个成员:req.session 默认是一个对象
  2. // 这是最简单的配置方式,暂且先不用关心里面参数的含义
  3. app.use(session({
  4. // 配置加密字符串,它会在原有加密基础之上和这个字符串拼起来去加密
  5. // 目的是为了增加安全性,防止客户端恶意伪造
  6. secret: 'itcast',
  7. resave: false,
  8. saveUninitialized: false // 无论你是否使用 Session ,我都默认直接给你分配一把钥匙
  9. }))

使用:

  1. // 添加 Session 数据
  2. req.session.foo = 'bar'
  3. // 获取 Session 数据
  4. req.session.foo

提示:默认 Session 数据是内存存储的,服务器一旦重启就会丢失,真正的生产环境会把 Session 进行持久化存储。


路由

参考文档:

一个非常基础的路由:

  1. var express = require('express')
  2. var app = express()
  3. // respond with "hello world" when a GET request is made to the homepage
  4. app.get('/', function (req, res) {
  5. res.send('hello world')
  6. })

路由方法

  1. // GET method route
  2. app.get('/', function (req, res) {
  3. res.send('GET request to the homepage')
  4. })
  5. // POST method route
  6. app.post('/', function (req, res) {
  7. res.send('POST request to the homepage')
  8. })

路由路径

This route path will match requests to the root route, /.

  1. app.get('/', function (req, res) {
  2. res.send('root')
  3. })

This route path will match requests to /about.

  1. app.get('/about', function (req, res) {
  2. res.send('about')
  3. })

This route path will match requests to /random.text.

  1. app.get('/random.text', function (req, res) {
  2. res.send('random.text')
  3. })

Here are some examples of route paths based on string patterns.

This route path will match acd and abcd.

  1. app.get('/ab?cd', function (req, res) {
  2. res.send('ab?cd')
  3. })

This route path will match abcd, abbcd, abbbcd, and so on.

  1. app.get('/ab+cd', function (req, res) {
  2. res.send('ab+cd')
  3. })

This route path will match abcd, abxcd, abRANDOMcd, ab123cd, and so on.

  1. app.get('/ab*cd', function (req, res) {
  2. res.send('ab*cd')
  3. })

This route path will match /abe and /abcde.

  1. app.get('/ab(cd)?e', function (req, res) {
  2. res.send('ab(cd)?e')
  3. })

Examples of route paths based on regular expressions:

This route path will match anything with an “a” in the route name.

  1. app.get(/a/, function (req, res) {
  2. res.send('/a/')
  3. })

This route path will match butterfly and dragonfly, but not butterflyman, dragonflyman, and so on.

  1. app.get(/.*fly$/, function (req, res) {
  2. res.send('/.*fly$/')
  3. })

动态路径

  1. Route path: /users/:userId/books/:bookId
  2. Request URL: http://localhost:3000/users/34/books/8989
  3. req.params: { "userId": "34", "bookId": "8989" }

定义动态的路由路径:

  1. app.get('/users/:userId/books/:bookId', function (req, res) {
  2. res.send(req.params)
  3. })

路由处理方法

app.route()

express.Router

Create a router file named router.js in the app directory, with the following content:

  1. const express = require('express')
  2. const router = express.Router()
  3. router.get('/', function (req, res) {
  4. res.send('home page')
  5. })
  6. router.get('/about', function (req, res) {
  7. res.send('About page')
  8. })
  9. module.exports = router

Then, load the router module in the app:

  1. const router = require('./router')
  2. // ...
  3. app.use(router)

在 Express 中获取客户端请求参数的三种方式

例如,有一个地址:/a/b/c?foo=bar&id=123

查询字符串参数

获取 ?foo=bar&id=123

  1. console.log(req.query)

结果如下:

  1. {
  2. foo: 'bar',
  3. id: '123'
  4. }

请求体参数

POST 请求才有请求体,我们需要单独配置 body-parser 中间件才可以获取。
只要程序中配置了 body-parser 中间件,我们就可以通过 req.body 来获取表单 POST 请求体数据。

  1. req.body
  2. // => 得到一个请求体对象

动态的路径参数

在 Express 中,支持把一个路由设计为动态的。例如:

  1. // /users/:id 要求必须以 /users/ 开头,:id 表示动态的,1、2、3、abc、dnsaj 任意都行
  2. // 注意::冒号很重要,如果你不加,则就变成了必须 === /users/id
  3. // 为啥叫 id ,因为是动态的路径,服务器需要单独获取它,所以得给它起一个名字
  4. // 那么我们就可以通过 req.params 来获取路径参数
  5. app.get('/users/:id', (req, res, next) => {
  6. console.log(req.params.id)
  7. })
  8. // /users/*/abc
  9. // req.params.id
  10. app.get('/users/:id/abc', (req, res, next) => {
  11. console.log(req.params.id)
  12. })
  13. // /users/*/*
  14. // req.params.id
  15. // req.params.abc
  16. app.get('/users/:id/:abc', (req, res, next) => {
  17. console.log(req.params.id)
  18. })
  19. // /*/*/*
  20. // req.params.users
  21. app.get('/:users/:id/:abc', (req, res, next) => {
  22. console.log(req.params.id)
  23. })
  24. // /*/id/*
  25. app.get('/:users/id/:abc', (req, res, next) => {
  26. console.log(req.params.id)
  27. })

中间件

参考文档:

Express 的最大特色,也是最重要的一个设计,就是中间件。一个 Express 应用,就是由许许多多的中间件来完成的。

为了理解中间件,我们先来看一下我们现实生活中的自来水厂的净水流程。

中间件

在上图中,自来水厂从获取水源到净化处理交给用户,中间经历了一系列的处理环节,我们称其中的每一个处理环节就是一个中间件。这样做的目的既提高了生产效率也保证了可维护性。

一个简单的中间件例子:打印日志

  1. app.get('/', (req, res) => {
  2. console.log(`${req.method} ${req.url} ${Date.now()}`)
  3. res.send('index')
  4. })
  5. app.get('/about', (req, res) => {
  6. console.log(`${req.method} ${req.url} ${Date.now()}`)
  7. res.send('about')
  8. })
  9. app.get('/login', (req, res) => {
  10. console.log(`${req.method} ${req.url} ${Date.now()}`)
  11. res.send('login')
  12. })

在上面的示例中,每一个请求处理函数都做了一件同样的事情:请求日志功能(在控制台打印当前请求方法、请求路径以及请求时间)。

针对于这样的代码我们自然想到了封装来解决:

  1. app.get('/', (req, res) => {
  2. // console.log(`${req.method} ${req.url} ${Date.now()}`)
  3. logger(req)
  4. res.send('index')
  5. })
  6. app.get('/about', (req, res) => {
  7. // console.log(`${req.method} ${req.url} ${Date.now()}`)
  8. logger(req)
  9. res.send('about')
  10. })
  11. app.get('/login', (req, res) => {
  12. // console.log(`${req.method} ${req.url} ${Date.now()}`)
  13. logger(req)
  14. res.send('login')
  15. })
  16. function logger (req) {
  17. console.log(`${req.method} ${req.url} ${Date.now()}`)
  18. }

这样的做法自然没有问题,但是大家想一想,我现在只有三个路由,如果说有10个、100个、1000个呢?那我在每个请求路由函数中都手动调用一次也太麻烦了。

好了,我们不卖关子了,来看一下我们如何使用中间件来解决这个简单的小功能。

  1. app.use((req, res, next) => {
  2. console.log(`${req.method} ${req.url} ${Date.now()}`)
  3. next()
  4. })
  5. app.get('/', (req, res) => {
  6. res.send('index')
  7. })
  8. app.get('/about', (req, res) => {
  9. res.send('about')
  10. })
  11. app.get('/login', (req, res) => {
  12. res.send('login')
  13. })
  14. function logger (req) {
  15. console.log(`${req.method} ${req.url} ${Date.now()}`)
  16. }

上面代码执行之后我们发现任何请求进来都会先在服务端打印请求日志,然后才会执行具体的业务处理函数。那这个到底是怎么回事?

中间件的组成

中间件的组成

中间件函数可以执行以下任何任务:

  • 执行任何代码
  • 修改 request 或者 response 响应对象
  • 结束请求响应周期
  • 调用下一个中间件

中间件分类

  • 应用程序级别中间件
  • 路由级别中间件
  • 错误处理中间件
  • 内置中间件
  • 第三方中间件

应用程序级别中间件

不关心请求路径:

  1. var app = express()
  2. app.use(function (req, res, next) {
  3. console.log('Time:', Date.now())
  4. next()
  5. })

限定请求路径:

  1. app.use('/user/:id', function (req, res, next) {
  2. console.log('Request Type:', req.method)
  3. next()
  4. })

限定请求方法:

  1. app.get('/user/:id', function (req, res, next) {
  2. res.send('USER')
  3. })

多个处理函数:

  1. app.use('/user/:id', function (req, res, next) {
  2. console.log('Request URL:', req.originalUrl)
  3. next()
  4. }, function (req, res, next) {
  5. console.log('Request Type:', req.method)
  6. next()
  7. })

多个路由处理函数:

  1. app.get('/user/:id', function (req, res, next) {
  2. console.log('ID:', req.params.id)
  3. next()
  4. }, function (req, res, next) {
  5. res.send('User Info')
  6. })
  7. // handler for the /user/:id path, which prints the user ID
  8. app.get('/user/:id', function (req, res, next) {
  9. res.end(req.params.id)
  10. })

最后一个例子:

  1. app.get('/user/:id', function (req, res, next) {
  2. // if the user ID is 0, skip to the next route
  3. if (req.params.id === '0') next('route')
  4. // otherwise pass the control to the next middleware function in this stack
  5. else next()
  6. }, function (req, res, next) {
  7. // render a regular page
  8. res.render('regular')
  9. })
  10. // handler for the /user/:id path, which renders a special page
  11. app.get('/user/:id', function (req, res, next) {
  12. res.render('special')
  13. })

路由级别中间件

创建路由实例:

  1. var router = express.Router()

示例:

  1. var app = express()
  2. var router = express.Router()
  3. // a middleware function with no mount path. This code is executed for every request to the router
  4. router.use(function (req, res, next) {
  5. console.log('Time:', Date.now())
  6. next()
  7. })
  8. // a middleware sub-stack shows request info for any type of HTTP request to the /user/:id path
  9. router.use('/user/:id', function (req, res, next) {
  10. console.log('Request URL:', req.originalUrl)
  11. next()
  12. }, function (req, res, next) {
  13. console.log('Request Type:', req.method)
  14. next()
  15. })
  16. // a middleware sub-stack that handles GET requests to the /user/:id path
  17. router.get('/user/:id', function (req, res, next) {
  18. // if the user ID is 0, skip to the next router
  19. if (req.params.id === '0') next('route')
  20. // otherwise pass control to the next middleware function in this stack
  21. else next()
  22. }, function (req, res, next) {
  23. // render a regular page
  24. res.render('regular')
  25. })
  26. // handler for the /user/:id path, which renders a special page
  27. router.get('/user/:id', function (req, res, next) {
  28. console.log(req.params.id)
  29. res.render('special')
  30. })
  31. // mount the router on the app
  32. app.use('/', router)

另一个示例:

  1. var app = express()
  2. var router = express.Router()
  3. // predicate the router with a check and bail out when needed
  4. router.use(function (req, res, next) {
  5. if (!req.headers['x-auth']) return next('router')
  6. next()
  7. })
  8. router.get('/', function (req, res) {
  9. res.send('hello, user!')
  10. })
  11. // use the router and 401 anything falling through
  12. app.use('/admin', router, function (req, res) {
  13. res.sendStatus(401)
  14. })

错误处理中间件

  1. app.use(function (err, req, res, next) {
  2. console.error(err.stack)
  3. res.status(500).send('Something broke!')
  4. })

内置中间件

  • express.static serves static assets such as HTML files, images, and so on.
  • express.json parses incoming requests with JSON payloads. NOTE: Available with Express 4.16.0+
  • express.urlencoded parses incoming requests with URL-encoded payloads. NOTE: Available with Express 4.16.0+

官方支持的中间件列表:

第三方中间件

官方中间件资源:http://expressjs.com/en/resources/middleware.html

早期的 Express 内置了很多中间件。后来 Express 在 4.x 之后移除了这些内置中间件,官方把这些功能性中间件以包的形式单独提供出来。这样做的目的是为了保持 Express 本身极简灵活的特性,开发人员可以根据自己的需求去灵活的定制。下面是官方提供的一些常用的中间件解决方案。

Middleware module Description Replaces built-in function (Express 3)
body-parser Parse HTTP request body. See also: body, co-body, and raw-body. express.bodyParser
compression Compress HTTP responses. express.compress
connect-rid Generate unique request ID. NA
cookie-parser Parse cookie header and populate req.cookies. See also cookies and keygrip. express.cookieParser
cookie-session Establish cookie-based sessions. express.cookieSession
cors Enable cross-origin resource sharing (CORS) with various options. NA
csurf Protect from CSRF exploits. express.csrf
errorhandler Development error-handling/debugging. express.errorHandler
method-override Override HTTP methods using header. express.methodOverride
morgan HTTP request logger. express.logger
multer Handle multi-part form data. express.bodyParser
response-time Record HTTP response time. express.responseTime
serve-favicon Serve a favicon. express.favicon
serve-index Serve directory listing for a given path. express.directory
serve-static Serve static files. express.static
session Establish server-based sessions (development only). express.session
timeout Set a timeout period for HTTP request processing. express.timeout
vhost Create virtual domains. express.vhost

中间件应用

输出请求日志中间件

功能:实现为任何请求打印请求日志的功能。

logger.js 定义并导出一个中间件处理函数:

  1. module.exports = (req, res, next) => {
  2. console.log(`${req.method} -- ${req.path}`)
  3. next()
  4. }

app.js 加载使用中间件处理函数:

  1. app.use(logger)

统一处理静态资源中间件

功能:实现 express.static() 静态资源处理功能

static.js 定义并导出一个中间件处理函数:

  1. const fs = require('fs')
  2. const path = require('path')
  3. module.exports = function static(pathPrefix) {
  4. return function (req, res, next) {
  5. const filePath = path.join(pathPrefix, req.path)
  6. fs.readFile(filePath, (err, data) => {
  7. if (err) {
  8. // 继续往后匹配查找能处理该请求的中间件
  9. // 如果找不到,则 express 会默认发送 can not get xxx
  10. return next()
  11. }
  12. res.end(data)
  13. })
  14. }
  15. }

app.js 加载并使用 static 中间件处理函数:

  1. // 不限定请求路径前缀
  2. app.use(static('./public'))
  3. app.use(static('./node_modules'))
  4. // 限定请求路径前缀
  5. app.use('/public', static('./public'))
  6. app.use('/node_modules', static('./node_modules'))

错误处理

参考文档:

常用 API

参考文档:

express

  • express.json
  • express.static
  • express.Router
  • express.urlencoded()

Application

  • app.set
  • app.get
  • app.locals

Request

  • req.app
  • req.query
  • req.body
  • req.cookies
  • req.ip
  • req.hostname
  • Req.method
  • req.params
  • req.path
  • req.get()

Response

  • res.locals
  • res.append()
  • res.cookie()
  • res.clearCookie()
  • res.download()
  • res.end()
  • res.json()
  • res.jsonp()
  • res.redirect()
  • res.render()
  • res.send()
  • res.sendStatus()
  • res.set()
  • res.status()

Router

  • router.all()
  • router.METHOD()
  • router.use()

小案例

案例Github仓库地址:https://github.com/lipengzhou/express-guestbook-case

零、准备

完整目录结构如下:

  1. .
  2. ├── node_modules npm安装的第三方包目录,使用 npm 装包会自动创建
  3. ├── public 页面需要使用的静态资源
  4. ├── css
  5. ├── js
  6. ├── img
  7. └── ...
  8. ├── views 所有视图页面(只存储 html 文件)
  9. ├── publish.html
  10. └── index.html
  11. ├── app.js 服务端程序入口文件,执行该文件会启动我们的 Web 服务器
  12. ├── db.json 这里充当我们的数据库
  13. ├── README.md 项目说明文档
  14. ├── package.json 项目包说明文件,存储第三方包依赖等信息
  15. └── package-lock.json npm的包锁定文件,用来锁定第三方包的版本和提高npm下载速度
  1. # 创建项目目录
  2. mkdir guestbook
  3. # 进入项目目录
  4. cd guestbook
  5. # 初始化 package.json 文件
  6. npm init -y
  7. # 将 Express 安装到项目中
  8. npm install express

一、Hello World

  1. // 0. 加载 Express
  2. const express = require('express')
  3. // 1. 调用 express() 得到一个 app
  4. // 类似于 http.createServer()
  5. const app = express()
  6. // 2. 设置请求对应的处理函数
  7. // 当客户端以 GET 方法请求 / 的时候就会调用第二个参数:请求处理函数
  8. app.get('/', (req, res) => {
  9. res.send('hello world')
  10. })
  11. // 3. 监听端口号,启动 Web 服务
  12. app.listen(3000, () => console.log('app listening on port 3000!'))

二、配置模板引擎

参见:Express - 使用模板引擎

三、路由设计

请求方法 请求路径 作用
GET / 渲染 index.html
GET /publish 渲染 publish.html
POST /publish 处理发表留言
  1. app.get('/', function (req, res) {
  2. // ...
  3. })
  4. app.get('/publish', function (req, res) {
  5. // ...
  6. })
  7. app.post('/publish', function (req, res) {
  8. // ...
  9. })

四、走通页面渲染跳转

  1. app.get('/', function (req, res) {
  2. res.render('index.html')
  3. })
  4. app.get('/publish', function (req, res) {
  5. res.render('publish.html')
  6. })

五、安装处理 Bootstrap 样式文件

安装 bootstrap 到项目中:

  1. npm install bootstrap

node_modules 目录开放出来:

  1. app.use('/node_modules/', express.static('./node_modules/'))

六、将数据库中的 post 渲染到首页

JavaScript 后台处理:

  1. app.get('/', function (req, res) {
  2. fs.readFile('./db.json', function (err, data) {
  3. if (err) {
  4. return res.render('500.html', {
  5. errMessage: err.message
  6. })
  7. }
  8. try {
  9. data = JSON.parse(data.toString())
  10. res.render('index.html', {
  11. posts: data.posts
  12. })
  13. } catch (err) {
  14. return res.render('500.html', {
  15. errMessage: err.message
  16. })
  17. }
  18. })
  19. })

index.html 页面模板字符串:

  1. <ul class="list-group">
  2. {{ each posts }}
  3. <li class="list-group-item">
  4. <span class="badge">{{ $value.time }}</span>
  5. <span>{{ $value.name }}</span>说:<span>{{ $value.content }}</span>
  6. </li>
  7. {{ /each }}
  8. </ul>

七、配置解析表单 post 请求体

参见:Express - 解析表单 post 请求体

八、处理 publish 表单提交

  1. app.post('/publish', function (req, res) {
  2. var body = req.body
  3. fs.readFile('./db.json', function (err, data) {
  4. if (err) {
  5. return res.render('500.html', {
  6. errMessage: err.message
  7. })
  8. }
  9. try {
  10. data = JSON.parse(data.toString())
  11. var posts = data.posts
  12. var last = posts[posts.length - 1]
  13. // 生成数据添加到 post 数组中
  14. posts.unshift({
  15. id: last ? last.id + 1: 1,
  16. name: body.name,
  17. content: body.content,
  18. time: moment().format('YYYY-MM-DD HH:mm:ss') // moment 是一个专门用来处理时间的 JavaScript 库
  19. })
  20. // 把对象转成字符串存储到文件中
  21. // try-catch 无法捕获异步代码的异常
  22. fs.writeFile('./db.json', JSON.stringify(data), function (err) {
  23. if (err) {
  24. return res.render('500.html', {
  25. errMessage: err.message
  26. })
  27. }
  28. // 代码执行到这里,说明写入文件成功了
  29. // 在 Express 中,我们可以使用 res.redirect() 实现服务端重定向的功能
  30. res.redirect('/')
  31. })
  32. } catch (err) {
  33. return res.render('500.html', {
  34. errMessage: err.message
  35. })
  36. }
  37. })
  38. })

九、案例优化:提取数据操作模块

  1. const {readFile, writeFile} = require('fs')
  2. const dbPath = './db.json'
  3. exports.getDb = getDb
  4. // 封装带来的好处:
  5. // 1. 可维护性
  6. // 2. 其次才是重用
  7. exports.addPost = (post, callback) => {
  8. getDb((err, dbData) => {
  9. if (err) {
  10. return callback(err)
  11. }
  12. // 获取数组中最后一个元素
  13. const last = dbData.posts[dbData.posts.length - 1]
  14. // 添加数据的 id 自动增长
  15. post.id = last ? last.id + 1 : 1
  16. // 创建时间
  17. post.createdAt = '2018-2-2 11:57:06'
  18. // 将数据添加到数组中(这里还并没有持久化存储)
  19. dbData.posts.push(post)
  20. // 将 dbData 对象转成字符串持久化存储到文件中
  21. const dbDataStr = JSON.stringify(dbData)
  22. writeFile(dbPath, dbDataStr, err => {
  23. if (err) {
  24. return callback(err)
  25. }
  26. // Express 为 res 响应对象提供了一个工具方法:redirect 可以便捷的重定向
  27. // res.redirect('/')
  28. callback(null)
  29. })
  30. })
  31. }
  32. function getDb (callback) {
  33. readFile(dbPath, 'utf8', (err, data) => {
  34. if (err) {
  35. return callback(err)
  36. }
  37. callback(null, JSON.parse(data))
  38. })
  39. }

十、案例总结