在Nuxt Bridge服务器中间件中实现MySQL连接复用的问题咨询
背景
我正在学习使用Nuxt,目前采用Nuxt Bridge(具备Nuxt3的大部分功能,同时兼容所需的nuxt-auth-next模块),基于nuxi/nitro开发API,使用Nuxt3的/server/api和/server/middleware目录。现有API路由(如/server/api/me.get.ts)中,每个路由都重复编写建立MySQL连接的代码,冗余且不够优雅,希望通过serverMiddleware为所有路由统一建立连接。
现有代码示例
// /server/api/me.get.ts import mysql, { RowDataPacket } from 'mysql2/promise' import { defineEventHandler, getRequestHeader } from 'h3' import { useRuntimeConfig } from '#imports' export default defineEventHandler(async (event) => { const config = useRuntimeConfig() try { const db = await mysql.createPool(config.mysql) // ... 从JWT令牌获取用户信息的核心逻辑 } catch (e) { // 错误处理 } })
我的疑问
- 这种使用serverMiddleware统一管理数据库连接的方式是否符合最佳实践?
- 尝试实现后无效,该如何正确操作?之前的尝试代码如下:
// /server/middleware/database.ts import type { IncomingMessage, ServerResponse } from 'http' import mysql from 'mysql2/promise' import { defineHandler } from 'h3' import { useRuntimeConfig } from '#imports' export default defineHandler( async (req: IncomingMessage, res: ServerResponse) => { const config = useRuntimeConfig() try { req['db'] = await mysql.createPool(config.mysql) } catch (e) { console.log('error') } } )
// /server/api/test.get.ts import type { IncomingMessage, ServerResponse } from 'http' import { defineHandler } from 'h3' import { useRuntimeConfig } from '#imports' export default defineHandler( async (req: IncomingMessage, res: ServerResponse) => { const [test] = req['db'].query('SELECT * FROM users') // 核心逻辑 } )
解答
1. 关于最佳实践
你的思路方向是对的,但需要调整:不要每次请求创建新的连接池,而是创建全局复用的连接池,再通过serverMiddleware把它挂载到请求上下文供路由使用。
连接池的设计本身就是为了复用数据库连接,避免频繁创建/销毁连接带来的性能损耗。如果每次请求都新建连接池,反而违背了连接池的初衷,这也是你之前代码的核心问题之一。
2. 正确实现步骤
步骤1:创建全局数据库连接池
新建/server/utils/db.ts,用来初始化并导出全局连接池,确保只初始化一次:
// /server/utils/db.ts import mysql from 'mysql2/promise' import { useRuntimeConfig } from '#imports' // 全局连接池实例,仅初始化一次 let pool: mysql.Pool | null = null export async function getDbPool() { if (!pool) { const config = useRuntimeConfig() // 从配置中读取MySQL连接信息 pool = mysql.createPool(config.mysql) } return pool }
步骤2:编写Server Middleware挂载连接池
修改/server/middleware/database.ts,使用Nitro的event对象而非原生req/res,将连接池挂载到event.context(这是Nitro中传递请求上下文的标准方式):
// /server/middleware/database.ts import { defineEventHandler } from 'h3' import { getDbPool } from '../utils/db' export default defineEventHandler(async (event) => { // 获取全局连接池并挂载到请求上下文 event.context.db = await getDbPool() })
步骤3:在API路由中使用连接池
修改API路由代码,从event.context获取连接池,同时注意query方法是异步的,需要添加await:
// /server/api/test.get.ts import { defineEventHandler } from 'h3' import type { RowDataPacket } from 'mysql2/promise' export default defineEventHandler(async (event) => { try { // 从上下文获取连接池,执行查询并指定返回数据类型 const [users] = await event.context.db.query<RowDataPacket[]>('SELECT * FROM users') return { success: true, data: users } } catch (error) { console.error('数据库查询失败:', error) return { success: false, message: '获取用户数据失败' } } })
步骤4:处理TypeScript类型提示(可选但推荐)
为了让TypeScript识别event.context.db的类型,新建/server/types/index.ts扩展H3的上下文类型:
// /server/types/index.ts import type { Pool } from 'mysql2/promise' declare module 'h3' { interface H3EventContext { db: Pool } }
之前代码的问题总结
- 每次请求新建连接池:性能低下,违背连接池设计初衷
- 直接操作原生
req/res:在Nitro中应该使用event对象的context属性传递上下文 - 未使用
await调用query:query是异步方法,必须等待执行完成才能获取结果
内容的提问来源于stack exchange,提问作者Luc Simons

