本文节选自 Next.js 应用开发实践
Next.js 没有中间件机制。首先让我简单解释一下什么是中间件,为什么我们需要中间件。
在 Express/Koa, 我们可以用中间件进入一个请求的生命周期,一个典型的中间件是一个函数,它接受 req
, res
和 next
参数:
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