express学习笔记

66 阅读9分钟

安装

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}`)
})

解释:

  1. app.get是路由定义:

路由定义采用如下结构,详情请看Express 基本路由

app.METHOD(PATH, HANDLER)
  • app 是 express 的实例。
  • METHOD 是 HTTP 请求方法
  • PATH 是服务器上的路径。
  • HANDLER 是在路由匹配时执行的函数。可以定义一个或多个处理程序函数。
  1. 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 的变量来表示。

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

  • 执行任何代码。
  • 对请求和响应对象进行更改。
  • 结束请求/响应循环。
  • 调用堆栈中的下一个中间件。

下面看看官方的一个示例图:

image.png

就是说,我们前面学过的路由方法,比如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('添加文章的时候发生错误')
  }
}

image.png

如果想设置状态码:

比如模拟token过期的情况:

throw new AppError('token过期', 401)

image.png

(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 会从上到下依次检查:

  1. /users/:id ✅ 匹配成功(:id = 'all'
  2. Express 停止继续匹配
  3. 执行第一个回调函数(即 findById('all')
  4. Mongoose 尝试把 'all' 转换为 ObjectId
  5. ❌ 报错: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运行项目入口文件:

image.png

然后ndb会打开自己的调试窗口:

image.png

注意:进行ndb [入口文件]的时候项目也同时启动了啊,不用再启动。

image.png

也可以在package.json中进行启动命令:

image.png 然后npm run debug就可以启动了。

下面发一个get请求进行debug,用postman发个请求:

image.png

image.png