编写 Nuxt 服务端中间件
简单来说,服务器中间件是在 Nuxt 中用作中间件的服务器端应用程序。自第八章 “添加服务器端框架” 以来,我们一直在 Koa 等服务器端框架下运行我们的 Nuxt 应用程序。如果你使用的是 Express,这是你的 package.json 文件中的 scripts 对象:
// package.json
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
"build": "nuxt build",
"start": "cross-env NODE_ENV=production node server/index.js",
"generate": "nuxt generate"
}
在这个 npm 脚本中,dev 和 start 脚本指示服务器从 /server/index.js 运行你的应用程序。这可能不是理想的,因为我们已经将 Nuxt 和服务器端框架紧密耦合在一起,并且会导致额外的配置工作。但是,我们可以告诉 Nuxt 不要在 /server/index.js 中附加到服务器端框架的配置,并保留我们原始的 Nuxt 运行脚本,如下所示:
// package.json
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate"
}
相反,我们可以通过使用 Nuxt 配置文件的 serverMiddleware 属性,让服务器端框架在 Nuxt 下运行。例如,请看以下代码:
// nuxt.config.js
export default {
serverMiddleware: [
'~/api'
]
}
与在客户端每个路由之前调用的路由中间件不同,服务器中间件总是在服务器端在 vue-server-renderer 之前调用。因此,服务器中间件可以用于服务器特定的任务,就像我们在之前的章节中使用 Koa 或 Express 所做的那样。因此,让我们在接下来的章节中探讨如何将 Express 和 Koa 用作我们的服务器中间件。
将 Express 用作 Nuxt 的服务端中间件
让我们使用 Express 作为 Nuxt 的服务器中间件创建一个简单的身份验证应用。我们将继续使用来自身份验证练习的客户端代码以及你在上一节中学习的单个路由中间件,在该练习中,用户需要提供用户名和密码才能访问受保护的页面。此外,我们将像以前一样使用 Vuex store 来集中管理经过身份验证的用户数据。此练习的主要区别在于,我们的 Nuxt 应用将作为中间件从服务器端应用中移出,而服务器端应用将作为中间件移入 Nuxt 应用中。因此,让我们按照以下步骤开始:
-
安装
cookie-session和body-parser作为服务器中间件,并在Nuxt配置文件中将我们的API路径添加到它们之后,如下所示:// nuxt.config.js import bodyParser from 'body-parser' import cookieSession from 'cookie-session' export default { serverMiddleware: [ bodyParser.json(), cookieSession({ name: 'express:sess', secret: 'super-secret-key', maxAge: 60000 }), '~/api' ] }请注意,
cookie-session是Express的基于cookie的会话中间件,它将会话存储在客户端的cookie中。相比之下,body-parser是Express的body解析中间件,就像你在第八章 “添加服务器端框架” 中了解到的Koa的koa-bodyparser一样。有关
Express的cookie-session和body-parser的更多信息,请访问 https://github.com/expressjs/cookie-session 和 https://github.com/expressjs/body-parser。 -
使用
index.js文件创建一个/api/目录,在该文件中导入并导出Express作为另一个服务器中间件:// api/index.js import express from 'express' const app = express() app.get('/', (req, res) => res.send('Hello World!')) // 导出服务器中间件 export default { path: '/api', handler: app } -
使用
npm run dev运行应用,你应该在localhost:3000/api中看到 “Hello World!” 消息。 -
在
/api/index.js中添加login和logout的post方法,如下所示:// api/index.js app.post('/login', (req, res) => { if (req.body.username === 'demo' && req.body.password === 'demo') { req.session.auth = { username: 'demo' }; return res.json({ username: 'demo' }); } res.status(401).json({ message: 'Bad credentials' }); }); app.post('/logout', (req, res) => { delete req.session.auth; res.json({ ok: true }); });在上面的代码中,当用户成功登录后,我们将经过身份验证的
payload作为auth存储到HTTP请求对象的Express会话中。然后,当用户注销时,我们将通过删除auth会话来清除它。 -
像编写单个路由中间件时一样,创建一个包含
state.js和mutations.js的store,如下所示:// store/state.js export default () => ({ auth: null, }) // store/mutations.js export default { setAuth (state, data) { state.auth = data } } -
就像编写单个路由中间件一样,在
store的actions.js文件中创建login和logoutaction 方法,如下所示:// store/actions.js import axios from 'axios' export default { async login({ commit }, { username, password }) { try { const { data } = await axios.post('/api/login', { username, password }) commit('setAuth', data) } catch (error) { // 处理错误... } }, async logout({ commit }) { await axios.post('/api/logout') commit('setAuth', null) } } -
将
nuxtServerInitaction 添加到store的index.js中,以便在刷新页面时从HTTP请求对象中的Express会话重新填充state:// store/index.js export const actions = { nuxtServerInit({ commit }, { req }) { if (req.session && req.session.auth) { commit('setAuth', req.session.auth) } } } -
最后,就像在单个路由中间件身份验证中一样,在
/pages/目录中创建一个包含表单的登录页面。使用你之前使用的相同的login和logout方法来dispatch store中的login和logoutaction 方法:// pages/index.vue <template> <form v-if="!$store.state.auth" @submit.prevent="login"> <p v-if="error" class="error">{{ error }}</p> <p>Username: <input v-model="username" type="text" name="username"></p> <p>Password: <input v-model="password" type="password" name="password"></p> <button type="submit">Login</button> </form> <div v-else> <p>Logged in as {{ $store.state.auth.username }}</p> <button @click="logout">Logout</button> </div> </template> <script> import axios from 'axios' export default { data () { return { error: null, username: '', password: '' } }, methods: { async login () { try { await this.$store.dispatch('login', { username: this.username, password: this.password }) this.error = null this.$router.push('/secured') } catch (error) { this.error = error.response.data.message || '登录失败' } }, async logout () { await this.$store.dispatch('logout') this.$router.push('/') } } } </script> -
使用
npm run dev运行应用。你应该拥有一个与之前工作方式相同的身份验证应用,但它不再从/server/index.js运行。你可以在我们的
GitHub仓库的/chapter-11/nuxt-universal/server-middleware/express/中找到上述源代码。
使用 serverMiddleware 属性可以使我们的 Nuxt 应用看起来更简洁、感觉更轻便,因为它摆脱了服务器端应用,你不觉得吗?通过这种方法,我们可以使其更灵活,因为我们可以使用任何服务器端框架或应用。例如,我们可以使用我们在下一节中看到的 Koa 来代替 Express。
将 Koa 用作 Nuxt 的服务端中间件
就像 Koa 和 Express 一样,Connect 是一个简单的框架,用于将各种中间件粘合在一起以处理 HTTP 请求。Nuxt 内部使用 Connect 作为服务器,因此大多数 Express 中间件都适用于 Nuxt 的服务器中间件。相比之下,Koa 中间件作为 Nuxt 的服务器中间件工作起来有点困难,因为 req 和 res 对象被隐藏并保存在 Koa 的 ctx 中。我们可以使用一个简单的 “Hello World” 消息来比较这三个框架,如下所示:
// Connect
const connect = require('connect')
const app = connect()
app.use((req, res, next) => res.end('Hello World'))
// Express
const express = require('express')
const app = express()
app.get('/', (req, res, next) => res.send('Hello World'))
// Koa
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => ctx.body = 'Hello World')
请注意,req 是 Node.js HTTP 请求对象,而 res 是 Node.js HTTP 响应对象。你可以随意命名它们,例如,用 request 代替 req,用 response 代替 res。从上面的比较中,你可以看到 Koa 处理这两个对象的方式与其他框架不同。因此,我们不能像在 Express 中那样将 Koa 用作 Nuxt 的服务器中间件,也不能在 serverMiddleware 属性中定义任何 Koa 中间件,而只能添加保存 Koa API 的目录路径。请放心,让它们在我们的 Nuxt 应用中作为中间件工作并不困难。让我们继续执行以下步骤:
-
添加我们想要使用
Koa创建API的路径,如下所示:// nuxt.config.js export default { serverMiddleware: [ '~/api' ] } -
导入
koa和koa-router,使用router创建一个 Hello World! 消息,然后将它们导出到/api/目录中的index.js文件:// api/index.js import Koa from 'koa' import Router from 'koa-router' const router = new Router() const app = new Koa() router.get('/', async (ctx, next) => { ctx.type = 'json' ctx.body = { message: 'Hello World!' } }) app.use(router.routes()) app.use(router.allowedMethods()) // 导出服务器中间件 export default { path: '/api', handler: app.callback() // 注意这里使用 app.callback() } -
导入
koa-bodyparser和koa-session,并将它们注册为/api/index.js文件中Koa实例的中间件,如下所示:// api/index.js import Koa from 'koa' import Router from 'koa-router' import bodyParser from 'koa-bodyparser' import session from 'koa-session' const router = new Router() const app = new Koa() const CONFIG = { key: 'koa:sess', maxAge: 60000, } app.use(session(CONFIG, app)) app.use(bodyParser()) router.get('/', async (ctx, next) => { ctx.type = 'json' ctx.body = { message: 'Hello World!' } }) app.use(router.routes()) app.use(router.allowedMethods()) // 导出服务器中间件 export default { path: '/api', handler: app.callback() } -
使用
Koa router创建登录(login)和注销(logout)路由,如下所示:// api/index.js import Koa from 'koa' import Router from 'koa-router' import bodyParser from 'koa-bodyparser' import session from 'koa-session' const router = new Router() const app = new Koa() const CONFIG = { key: 'koa:sess', maxAge: 60000, } app.use(session(CONFIG, app)) app.use(bodyParser()) router.get('/', async (ctx, next) => { ctx.type = 'json' ctx.body = { message: 'Hello World!' } }) router.post('/login', async (ctx, next) => { let request = ctx.request.body || {} if (request.username === 'demo' && request.password === 'demo') { ctx.session.auth = { username: 'demo' } ctx.body = { username: 'demo' } } else { ctx.throw(401, 'Bad credentials') } }) router.post('/logout', async (ctx, next) => { ctx.session = null ctx.body = { ok: true } }) app.use(router.routes()) app.use(router.allowedMethods()) // 导出服务器中间件 export default { path: '/api', handler: app.callback() }在上面的代码中,就像前面
Express示例中一样,当用户成功登录后,我们将经过身份验证的payload作为auth存储到Koa上下文对象中的Koa会话中。然后,当用户注销时,我们将通过将会话设置为null来清除auth会话。 -
创建一个包含
state、mutations和actions的store,就像你在Express示例中所做的那样。此外,在store的index.js文件中创建nuxtServerInit,就像你在编写单个路由中间件时所做的那样:// store/index.js export const actions = { nuxtServerInit({ commit }, { req }) { if (req.ctx.session && req.ctx.session.auth) { commit('setAuth', req.ctx.session.auth) } } } -
和以前一样,在
/pages/目录中创建表单登录(login)和注销(logout)方法,以dispatch store中的action方法:// pages/index.vue <template> <form v-if="!$store.state.auth" @submit.prevent="login"> //... </form> <div v-else> <p>Logged in as {{ $store.state.auth.username }}</p> <button @click="logout">Logout</button> </div> </template> <script> import axios from 'axios' export default { methods: { async login () { try { const { data } = await axios.post('/api/login', { username: this.username, password: this.password }) this.error = null this.$router.push('/secured') } catch (error) { this.error = error.response.data.message || '登录失败' } }, async logout () { await this.$store.dispatch('logout') this.$router.push('/') } } } </script> -
使用
npm run dev运行应用。你应该拥有一个与前面Express示例中工作方式相同的身份验证应用,但它不再从/server/index.js运行。你可以在我们的
GitHub仓库的/chapter-11/nuxt-universal/server-middleware/koa/中找到此示例的完整源代码。
根据你的偏好,你可以在你的下一个项目中使用 Express 或 Koa 作为 Nuxt 的服务器中间件。在本书中,我们主要使用 Koa,因为它更简洁。你甚至可以创建自定义服务器中间件,而无需使用它们中的任何一个。让我们在下一节看看如何创建自定义服务器中间件。
创建自定义服务端中间件
由于 Nuxt 内部使用 Connect 作为服务器,因此我们可以添加自定义中间件,而无需 Koa 或 Express 等外部服务器。你可以开发复杂的 Nuxt 服务器中间件,就像我们在前面几节中使用 Koa 和 Express 所做的那样。但是,让我们不要无休止地重复我们已经做过的事情。让我们创建一个非常基本的自定义中间件,它打印 “Hello World” 消息,以确认从基本中间件构建复杂中间件的可行性,请按照以下步骤操作:
-
添加我们想要创建自定义中间件的路径:
// nuxt.config.js serverMiddleware: [{ path: '/api', handler: '~/api/index.js' }] -
将
API路由添加到/api/目录中的index.js文件:// api/index.js export default function (req, res, next) { res.end('Hello world!') } -
使用
npm run dev运行应用并导航到localhost:3000/api。你应该在屏幕上看到 “Hello World!” 消息。你可以参考
Connect的文档 https://github.com/senchalabs/connect 以获取更多信息。此外,你可以在我们的GitHub仓库的/chapter-11/nuxt-universal/server-middleware/custom/中找到此示例的源代码。
做得好!你已经成功完成了另一个关于 Nuxt 的重要章节。在继续下一章之前,让我们总结一下你到目前为止学到的内容。