安装
npm install express
typescript 还要安装:
npm i @types/express
简单的hello world接口
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 on port ${port}`)
})
解释:
- app.get是路由定义:
路由定义采用如下结构,详情请看Express 基本路由。
app.METHOD(PATH, HANDLER)
app是express的实例。METHOD是 HTTP 请求方法PATH是服务器上的路径。HANDLER是在路由匹配时执行的函数。可以定义一个或多个处理程序函数。
- app.listen 监听指定的端口号:
参数:
app.listen([port[, host[, backlog]]][, callback])
- 方括号 [] 表示参数是可选的。
- 嵌套的方括号表示 host 和 backlog(很少使用,可以忽略) 只有在提供了前面的参数(如 port)时才有效。
- 最后的 [callback] 表示回调函数是可选的,且可以在任何参数组合后使用。只有成功启动的时候才执行callback函数。
注意事项:
如果省略端口或端口为 0,操作系统将分配一个随机的未使用端口。
Express 应用程序生成器
可使用应用程序生成器工具 (express-generator) 快速创建应用程序框架。详情请看Express 应用程序生成器。
路由深入
路由方法:
// GET method route
app.get('/', (req, res) => {
res.send('GET request to the homepage')
})
// POST method route
app.post('/', (req, res) => {
res.send('POST request to the homepage')
})
// delete, patch...等等也类似
app.all() :
app.all() 是 Express 提供的一种特殊路由方法,与常见的 app.get()、app.post() 等不同,它不绑定特定的 HTTP 方法(如 GET、POST 等)。相反,它会匹配所有 HTTP 请求方法(GET、POST、PUT、DELETE 等等),只要请求的路径匹配指定的路由。
比如:
app.all('/secret', (req, res, next) => {
console.log('Accessing the secret section ...')
next() // pass control to the next handler
})
使用场景:
可以用于给未知路由返回信息:
/**
* 要放到所有路由的最下面
* 当前面的路由都不匹配的时候,就轮到这个路由来处理。
**/
// 处理未知路由
app.all('{*splat}', (req, res, next) => {
next(new AppError(`Can't find ${req.originalUrl} on this server!`, 404))
})
app.route():
一般情况下我们是这样写路由的:
// 获取所有用户信息
app.get('/user', (req, res) => {
res.send('获取所有用户信息')
})
// 添加用户
app.post('/user', (req, res) => {
res.send('添加用户')
})
// 删除用户
app.delete('/user/:id', (req, res) => {
res.send('删除用户')
})
// 更新用户信息
app.patch('/user/:id', (req, res) => {
res.send('更新用户')
})
但是这样写很啰嗦,我们可以改成这样。
// 获取用户信息和添加用户信息
app.route('/user')
.get((req, res) => {
res.send('获取所有用户信息')
})
.post((req, res) => {
res.send('添加用户')
})
// 删除用户和更新用户
app.route('/user/:id')
.delete('/user/:id', (req, res) => {
res.send('删除用户')
})
.patch('/user/:id', (req, res) => {
res.send('更新用户')
})
express.Router:
使用 express.Router可以模块化我们路由。
比如跟用户有关的路由放到一个文件userRouter.js中:
const express = require('express')
const router = express.Router()
// 获取所有用户信息
router.get('/user', (req, res) => {
res.send('获取所有用户信息')
})
// 添加用户
router.post('/user', (req, res) => {
res.send('添加用户')
})
// 删除用户
router.delete('/user/:id', (req, res) => {
res.send('删除用户')
})
// 更新用户信息
router.patch('/user/:id', (req, res) => {
res.send('更新用户')
})
module.exports = router
然后在应用程序中装入用户的路由模块:
const userRouter = require('./userRouter.js')
// ...
app.use(userRouter)
中间件(middlew)
中间件函数能够访问请求对象、响应对象 以及应用程序的请求/响应循环中的下一个中间件函数。下一个中间件函数通常由名为 next 的变量来表示。
中间件函数可以执行以下任务:
- 执行任何代码。
- 对请求和响应对象进行更改。
- 结束请求/响应循环。
- 调用堆栈中的下一个中间件。
下面看看官方的一个示例图:
就是说,我们前面学过的路由方法,比如app.get, app.post()等的处理函数,也可以写成中间件的形式。
这里next可以这样理解:当前这个中间件完成自己的工作以后,调用 next(),以将控制权传递给下一个中间件函数。
中间件挂载方法:
应用层中间件
应用层中间件可以通过使用 app.use() 和 app.METHOD() 函数将应用级别的中间件绑定到 app 对象实例上,这里的就是get, post等http方法。
编写第一个实用的中间件:
const express = require('express')
const app = express()
app.use((req, res, next) => {
console.log('Time:', Date.now())
next()
})
这个示例展示了一个没有挂载路径的中间件函数。每次应用程序接收到请求时都会执行该函数。
这里插一嘴,app.use()的参数也可以写请求路径的,具体参数:
app.use([path,] callback [, callback...])
无论你有没有写path参数,'app.use()'匹配所有的http请求方法,然后执行指定的操作。
注意,定义中间件的时候一定要执行next(),不然下面的中间件或者处理函数都不会执行:
app.use((req, res, next) => {
console.log(req.body)
})
// 因为上一个middleware没有调用next(),所以这个middleware和这下面的middleware永远不会执行
app.use((req, res, next) => {
console.log(req.body)
next()
})
路由器层中间件
路由级中间件与应用级中间件的工作方式相同,只是它绑定到了一个 express.Router 实例上。
const router = express.Router()
跟应用层中间件相似,使用 router.use() 和 router.METHOD() 函数加载路由级中间件。
const express = require('express')
const app = express()
const router = express.Router()
// a middleware function with no mount path. This code is executed for every request to the router
router.use((req, res, next) => {
console.log('Time:', Date.now())
next()
})
// handler for the /user/:id path
router.get('/user/:id', (req, res, next) => {
console.log(req.params.id)
res.json({
message: 'succuss'
})
})
// mount the router on the app
app.use('/', router)
错误处理中间件
以与其他中间件函数相同的方式定义错误处理中间件函数,但需要四个参数而不是三个,具体参数为 (err, req, res, next) :
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})
内置中间件
Express 具有以下内置中间件函数(中间件):
- express.static 用于提供静态资源,例如 HTML 文件、图像等。
- express.json 用于解析带有 JSON 载体的传入请求。
- express.urlencoded 解析带有 URL 编码负载的传入请求。
第三方中间件
更多第三方中间件:www.expressjs.com.cn/resources/m…
下面举morgan演示怎么使用第三方中间件,Morgan是一个日志打印的中间件,Express morgan middleware
安装:
npm install morgan
用法:
const morgan = require('morgan')
// 使用中间件
app.use(morgan('dev'))
错误处理
错误处理是指 Express 如何捕获和处理同步和异步发生的错误。
路由处理程序和中间件中的同步代码发生的错误无需额外处理。如果同步代码抛出错误,Express 会捕获并处理它。例如:
app.get('/', (req, res) => {
throw new Error('BROKEN') // Express will catch this on its own.
})
对于路由处理程序和中间件调用的异步函数返回的错误,您必须将它们传递给 next() 函数,Express 将会捕获并处理这些错误。例如:
app.get('/users', async (req, res, next) => {
try {
const users = await User.find(); // 这是mongoose操作mongodb数据库,返回promise
res.json(users);
} catch (err) {
next(err);
}
});
注意:从 Express 5 开始,当路由处理程序和中间件返回的 Promise 被拒绝或抛出错误时,它们将自动调用 next()。例如:
以前必须手动传递错误:
// Express 4.x
app.get('/user/:id', async (req, res, next) => {
try {
const user = await getUserById(req.params.id); // 这里是数据库操作
res.send(user);
} catch (err) {
next(err); // 必须手动传递错误
}
});
但是从 Express 5 开始:
app.get('/user/:id', async (req, res, next) => {
const user = await getUserById(req.params.id); // 这里是数据库操作
res.send(user);
});
注意:如果你向 next() 函数传递任何内容(除了字符串 'route' ),Express 会将当前请求视为错误,并跳过所有剩余的非错误处理路由和中间件函数,直接将控制权传递给错误处理中间件(形如 (err, req, res, next) 的函数)。
编写自己的错误处理程序
首先我们创建一个自定义错误类AppError:
class AppError extends Error {
constructor(message, statusCode) {
super(message)
this.statusCode = statusCode // 用于设置状态码
// 这是AppError
this.name = 'AppError'
// 修复原型链
Object.setPrototypeOf(this, AppError.prototype)
}
}
module.exports = AppError
然后建一个globalErrorHancler.js文件:
const AppError = require('../utils/appError')
const globalErrorHandler = (err, req, res, next) => {
// 判断是不是业务错误
if (err instanceof AppError) {
console.log(err.stack) // 打印错误信息
// 判断有没有指定状态码
if (err.statusCode) {
return res.status(err.statusCode).json({
message: err.message,
code: 0,
})
} else {
// 如果没有指定状态码
return res.json({
message: err.message,
code: 0,
})
}
}
console.log('❌ Unexpected error:', err)
res.status(500).json({
message: '服务器内部错误',
code: 0,
})
}
module.exports = globalErrorHandler
然后用把这个错误处理中间件挂载到express实例中:
const express = require('express')
const app = express()
// 导入这个全局错误处理器
const globalErrorHandler = require('./middleware/globalErrorHandler')
// 挂载错误处理中间件
app.use(globalErrorHandler)
在路由处理函数中使用:
const Article = require('../models/article')
const AppError = require('../utils/appError')
/**
* 添加文章
* @param req
* @param res
* @param next
*/
exports.addArticle = async (req, res, next) => {
try {
const article = new Article(req.body)
const saved = await article.save()
res.json({
code: 0,
message: 'success',
data: saved,
})
} catch (error) {
throw new AppError('添加文章的时候发生错误')
}
}
如果想设置状态码:
比如模拟token过期的情况:
throw new AppError('token过期', 401)
(req, res, next)=>{}这里什么时候需要参数next
路由处理函数(如 (req, res) 或 (req, res, next))是中间件(middleware)的一种。中间件是按顺序执行的函数,负责处理 HTTP 请求并生成响应。next 参数是一个回调函数,用于将控制权传递给下一个中间件或路由处理器。
什么时候需要 next:
- 如果当前中间件(或路由处理函数)不结束请求-响应周期(即不调用 res.send、res.json 等方法),需要调用 next() 来将控制权传递给下一个中间件。
- 如果你想在当前中间件中处理错误并将错误传递给 Express 的错误处理中间件,必须使用 next(error)。
- 如果你的路由处理函数是中间件链的一部分(例如,多个中间件处理同一个请求),需要调用 next()。
什么时候不需要 next:
- 如果当前中间件或路由处理函数结束了请求-响应周期(例如,通过 res.send、res.json、res.status 等发送了响应),就不需要调用 next()。
- 如果你的路由处理函数是链中的最后一个处理器,且无需将控制权传递给其他中间件,通常不需要 next。
express请求参数获取方式
具体可以在这里查阅:expressjs.com/en/5x/api.h…
application/json
官方文档:expressjs.com/en/5x/api.h…
要通过req.body获取,默认情况下为空对象,当您使用如 express.json()等正文解析中间件时会填充该对象。
Query 参数
官方文档:expressjs.com/en/5x/api.h…
req.query
Pathvariable
官方文档:expressjs.com/en/5x/api.h…
// 路径
/user/:name
// 获取参数
console.dir(req.params.name)
Express路由匹配顺序
express中的路由是从上往下匹配的。
比如:
// 根据id获取用户
router.get('/users/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id)
// 如果用户不存在
if (!user) return res.status(404).json({ message: '没找到这个用户' })
res.json({ message: 'success', data: user })
} catch (error) {
res.status(400).json({
message: 'failed',
error: error,
})
}
})
// 查询所有用户
router.get('/users/all', async (req, res) => {
try {
console.log(req)
const users = await User.find()
res.json({ message: 'success', data: users })
} catch (error) {
res.status(400).json({
message: 'failed',
error: error,
})
}
})
第二个方法查询所有用户不会被执行的,为什么呢?
现在的路径是:
1️⃣ router.get('/users/:id', ...)
2️⃣ router.get('/users/all', ...)
当请求:
GET /users/all
Express 会从上到下依次检查:
/users/:id✅ 匹配成功(:id = 'all')- Express 停止继续匹配
- 执行第一个回调函数(即
findById('all')) - Mongoose 尝试把
'all'转换为ObjectId - ❌ 报错:
CastError: Cast to ObjectId failed for value "all"
因此,第二个 /users/all 根本不会被执行。
解决办法:让具体路由在前
越具体的路径应该放在越前面。
// ✅ 先写具体路径
router.get('/users/all', async (req, res) => {
try {
const users = await User.find()
res.json({ message: 'success', data: users })
} catch (error) {
res.status(400).json({ message: 'failed', error })
}
})
// ✅ 再写带参数的通用路径
router.get('/users/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id)
if (!user) return res.status(404).json({ message: '没找到这个用户' })
res.json({ message: 'success', data: user })
} catch (error) {
res.status(400).json({ message: 'failed', error })
}
})
怎么调试nodejs项目
推荐使用Google的ndb包来调试nodejs项目
ndb GitHub地址:github.com/GoogleChrom…
npm地址:www.npmjs.com/package/ndb
虽然这个包很久没有更新了,但是还是可以使用。
推荐全局安装:
npm i ndb --global
然后把本地的项目停掉,ndb运行项目入口文件:
然后ndb会打开自己的调试窗口:
注意:进行ndb [入口文件]的时候项目也同时启动了啊,不用再启动。
也可以在package.json中进行启动命令:
然后
npm run debug就可以启动了。
下面发一个get请求进行debug,用postman发个请求: