Express 是什么
Express 是一个快速,简单,极简的 Node.js web 应用开发框架。通过它,可以轻松的构建各种 web 应用。例如
- 接口服务
- 传统的 web 网站
- 开发工具集成等
- …
Express 本身是极简的,仅仅提供了 web 开发的基础功能,但是它通过中间件的方式集成了许许多多的外部插件来处理 HTTP 请求。
-
body-parser:解析 HTTP 请求体
-
compression:压缩 HTTP 响应
-
cookie-parser:解析 cookie 数据
-
cors:处理跨域资源请求
-
morgan:HTTP 请求日志记录
-
…
Express 中间件的特性固然强大,但是它所提供的灵活性是一把双刃剑。
-
它让 Express 本身变得更加灵活和简单
-
缺点在于虽然有一些中间件包可以解决几乎所有问题或需求,但是挑选合适的包有时也会成为一个挑战
Express 不对 Node.js 已有的特性进行二次抽象,只是在它之上扩展了 web 应用所需的基本功能。
-
内部使用的还是 http 模块
-
请求对象继承自 http.IncomingMessage
-
响应对象继承自:http.ServerResponse
-
…
有很多流行框架基于 Express。
- LoopBack:高度可扩展的开源 Node.js 框架,用于快速创建动态的端到端 REST API。
- Sails:用于Node.js的 MVC 框架,用于构建实用的,可用于生产的应用程序。
- NestJs:一个渐进式的 Node.js 框架,用于在 TypeScript 和
JavaScript(ES6,ES7,ES8)之上构建高效,可扩展的企业级服务器端应用程序。 - …
Express 的开发作者是知名的开源项目创建者和协作者 TJ Holowaychuk。
- GitHub:https://github.com/tj
- Express、commander、ejs、co、Koa…
Express 特性
-
简单易学
-
丰富的基础 API 支持,以及常见的 HTTP 辅助程序,例如重定向、缓存等
-
强大的路由功能
-
灵活的中间件
-
高性能
-
非常稳定(它的源代码几乎百分百的测试覆盖率)
-
视图系统支持 14 个以上的主流模板引擎
Express 应用场景
- 传统的 Web 网站
- Ghost
- 接口服务
- 服务端渲染中间层
- 开发工具
- JSON Server
- webpack-dev-server
Express 起步
Hello World
使用 Express 创建一个 web 服务,输出 Hello World 响应内容
步骤
mkdir myapp
cd myapp
npm init
npm install express
touch app.js
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
路由基础
路由是指确定应用程序如何响应客户端对特定端点的请求,该特定端点是URI(或路径)和特定的HTTP请求方法(GET,POST等)。
每个路由可以具有一个或多个处理程序函数,这些函数在匹配该路由时执行。
路由定义采用以下结构:
app.METHOD(PATH, HANDLER)
- app 是 Express 实例
- METHOD 是小写的 HTTP
- 请求方法 PATH 是服务器上的路径 HANDLER
- 是当路由匹配时执行的功能
示例。
在根路径响应 Hello World!:
app.get('/', function (req, res) {
res.send('Hello World!')
})
在根路由响应 POST 请求:
app.post('/', function (req, res) {
res.send('Got a POST request')
})
响应对 /user 路径的 PUT 请求:
app.put('/user', function (req, res) {
res.send('Got a PUT request at /user')
})
响应对 /user 路由的 DELETE 请求:
app.delete('/user', function (req, res) {
res.send('Got a DELETE request at /user')
})
请求和响应
Express 应用使用路由回调函数的参数:request 和 response 对象来处理请求和响应的数据。app.get('/', function (req, res) {
// --
})
Express 不对 Node.js 已有的特性进行二次抽象,只是在它之上扩展了 web 应用所需的基本功能。
- 内部使用的还是 http 模块
- 请求对象继承自 http.IncomingMessage
- 响应对象继承自:http.ServerResponse
请求对象
req 对象代表 HTTP 请求,并具有请求查询字符串,参数,正文,HTTP 标头等的属性。在本文档中,按照约定,该对象始终称为
req(HTTP 响应为 res),但其实际名称由您正在使用的回调函数的参数确定。
属性:
req.app
req.baseUrl
req.body
req.cookies
req.fresh
req.hostname
req.ip
req.ips
req.method
req.originalUrl
req.params
req.path
req.protocol
req.query
req.route
req.secure
req.signedCookies
req.stale
req.subdomains
req.xhr
方法
req.accepts()
req.acceptsCharsets()
req.acceptsEncodings()
req.acceptsLanguages()
req.get()
req.is()
req.param()
req.range()
响应对象
res 对象表示 Express 应用在收到 HTTP 请求时发送的 HTTP 响应。 在本文档中,按照约定,该对象始终称为 res(并且
HTTP 请求为 req),但其实际名称由您正在使用的回调函数的参数确定。
属性:
• res.app
• res.headersSent
• res.locals
方法:
• res.append()
• res.attachment()
• res.cookie()
• res.clearCookie()
• res.download()
• res.end()
• res.format()
• res.get()
• res.json()
• res.jsonp()
• res.links()
• res.location()
• res.redirect()
• res.render()
• res.send()
• res.sendFile()
• res.sendStatus()
• res.set()
• res.status()
• res.type()
• res.vary()
案例
通过该案例创建一个简单的 CRUD 接口服务,从而掌握 Express 的基本用法。
需求:实现对任务清单的 CRUD 接口服务。
• 查询任务列表
• GET /todos
• 根据 ID 查询单个任务
• GET /todos/:id
• 添加任务
• POST /todos
• 修改任务
• PATCH /todos
• 删除任务
• DELETE /todos/:id
准备数据文件 db.json
{
"todos": [
{
"title": "吃饭",
"id": 1
},
{
"title": "睡觉",
"id": 3
}
],
"users": []
}
封装数据操作模块 db.js
const fs = require('fs')
const { promisify } = require('util')
const path = require('path')
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
const dbPath = path.join(__dirname, './db.json')
exports.getDb = async () => {
const data = await readFile(dbPath, 'utf8')
return JSON.parse(data)
}
exports.saveDb = async db => {
const data = JSON.stringify(db, null, ' ')
await writeFile(dbPath, data)
}
Read 查询列表
app.get('/todos', async (req, res) => {
try {
const db = await getDb()
res.status(200).json(db.todos)
} catch (err) {
res.status(500).json({
error: err.message
})
}
})
Read 根据 id 查询单个
app.get('/todos/:id', async (req, res) => {
try {
const db = await getDb()
const todo = db.todos.find(todo => todo.id === Number.parseInt(req.params.id))
if (!todo) {
return res.status(404).end()
}
res.status(200).json(todo)
} catch (err) {
res.status(500).json({
error: err.message
})
}
})
Create 添加
app.post('/todos', async (req, res) => {
try {
// 1. 获取客户端请求体参数
const todo = req.body
// 2. 数据验证
if (!todo.title) {
return res.status(422).json({
error: 'The field title is required.'
})
}
// 3. 数据验证通过,把数据存储到 db 中
const db = await getDb()
const lastTodo = db.todos[db.todos.length - 1]
todo.id = lastTodo ? lastTodo.id + 1 : 1
db.todos.push(todo)
await saveDb(db)
// 4. 发送响应
res.status(201).json(todo)
} catch (err) {
res.status(500).json({
error: err.message
})
}
})
Update 更新
app.patch('/todos/:id', async (req, res) => {
try {
// 1. 获取表单数据
const todo = req.body
// 2. 查找到要修改的任务项
const db = await getDb()
const ret = db.todos.find(todo => todo.id === Number.parseInt(req.params.id))
if (!ret) {
return res.status(404).end()
}
Object.assign(ret, todo)
await saveDb(db)
res.status(200).json(ret)
} catch (err) {
res.status(500).json({
error: err.message
})
}
})
Destroy 删除
app.delete('/todos/:id', async (req, res) => {
try {
const todoId = Number.parseInt(req.params.id)
const db = await getDb()
const index = db.todos.findIndex(todo => todo.id === todoId)
if (index === -1) {
return res.status(404).end()
}
db.todos.splice(index, 1)
await saveDb(db)
res.status(204).end()
} catch (err) {
res.status(500).json({
error: err.message
})
}
})