1
本文节选自 Next.js 应用开发实践

Next.js 没有中间件机制。首先让我简单解释一下什么是中间件,为什么我们需要中间件。

在 Express/Koa, 我们可以用中间件进入一个请求的生命周期,一个典型的中间件是一个函数,它接受 req, resnext 参数:

function exampleMiddleware(req, res, next) {
  if (/** ...*/) {
    req.foo = 'bar'
      next()
  } else {
    res.statusCode = 403
    res.send('forbiddon')
  }
}
这个中间件的模式起源于一个 Node.js Web 框架 connect, 早期的 Express 也基于 Connect 开发,于是很多框架也兼容了这种模式,所以这种中间件模式我们通常称为 connect 中间件。

在中间件里,我们可以:

  • req 对象注入一些属性,这些属性可以被下一个中间件或者 controller 获取到。
  • 可以通过不执行 next() 来中止请求,同时修改 res 的属性从而改变 response 的状态。

这使得中间件可以很好地使代码在不同的路由之间重用。假设我们需要在一个路由跟据 cookies 获取用户信息,我们可以把这个获取用户信息的方法写成中间件,然后把用户信息注入到 req.user,这样所以使用了这个中间件的路由可以通过 req.user 取得用户信息。而且在中间件中,如果判断用户没有登录,可以中止这个请求,并返回 403.

下面是 Express 编写和使用中间件的例子:

function authMiddleware(req, res, next) {
  // 假设 cookies 中用 `token` 保存用户信息
  if (req.cookies.token) {
    const user = getUserByToken(req.cookies.token)
    req.user = user
    next()
  } else {
    // cookies.token 不存在,中止请求并返回 403
    res.statusCode = 403
    res.send('please sign in first')
  }
}

// 不使用这个中间件的路由
app.get('/', (req, res) => {
  res.send('hello world')
})

// 使用这个中间件的路由
app.get('/profile', authMiddleware, (req, res) => {
  // 可以通过 `req.user` 取得用户信息
  res.send(`welcome! ${req.user.name}`)
})

如果在 Next.js 要做同样的事,我们会这么做:

// pages/api/example.ts

function auth(req, res) {
  if (req.cookies.token) {
    const user = getUserByToken(req.cookies.token)
    return user
  } else {
    // 用户未登录
    res.status(403)
    res.send('please sign in first')
  }
}

export default (req, res) => {
  if (req.method === 'GET') {
    res.send('hello')
  } else if (req.method === 'POST') {
    const user = auth(req, res)
    console.log('do other things')
      res.send(`welcome! ${user.name}`)
  }
}

但在 Next.js, 我们没有任何办法中止请求。理论上 console.log('do other things') 在用户未登录时不应该被执行。

使用 next-connect

要在 Next.js 中像 Express/Koa 这样使用 connect 中间件,我们可以使用 next-connect 这个库。

安装 next-connect:

$ yarn add next-connect

现在,让我们用 next-connect 重写上面的例子:

// pages/api/example.ts

import nc from 'next-connect'

function authMiddleware(req, res, next) {
  res.status(403)
  res.send('please sign in first')
}

// 用 `nc()` 创建一个 api handler
const handler = nc()
  .get((req, res) => {
    res.send('hello')
  })
  .post(authMiddleware, (req,res) => {
    res.send('hello')
  })

export default handler

可以看到,现在我们在 Next.js 的API route 可以像在 Express 一样使用中间件。

authMiddleware中,我们返回了一个 403,并且没有执行 next(), 模拟了用户未登录的情况。由于 next() 没有执行,这个 POST 请求不会执行这个 POST handler 的代码。

next-connect 的另一个好处是,我们可以用.get(), .post(), put() 这样的 helper 来创建对应的 handler, 而不需要用 if (req.method === XXX) 这样的判断。让代码更好读。

因为 next-connect 兼容 connect 中间件,所以我们可以直接用社区上成熟的 connect 中间件,例如用于修改跨域设置的中间件 cors:

安装 cors:

$ yarn add cors
// pages/api/example.ts

import nc from 'next-connect'
+ import * as cors from 'cors'

const corsOptions = {
  origin: 'http://example.com',
  optionsSuccessStatus: 200
}

const handler = nc()
+    .use(cors(corsOptions))
  .get((req, res) => {
    res.send('hello')
  })
  .post(authMiddleware, (req,res) => {
    res.send('hello')
  })

export default handler

Randy
134 声望11 粉丝

野生前端工程师