编写 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
和logout
action 方法,如下所示:// 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) } }
-
将
nuxtServerInit
action 添加到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
和logout
action 方法:// 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
的重要章节。在继续下一章之前,让我们总结一下你到目前为止学到的内容。