Node.js环境下如何在服务端与客户端JS文件间共享MySQL连接
我在本站看到过类似问题,但都和我遇到的情况不完全一致,因此发帖提问,希望没有重复已有内容。
我是Node.js与socket.io的新手,目前正在开发一款多聊天室应用,还请大家多多包涵。
我目前卡在代码模块化拆分的步骤,项目目录结构如下:
我的index.js用于启动express服务器、配置基础socket.io连接,按照我的理解,这个入口index.js文件属于服务端JS。
Index.js
const path = require('path') const http = require('http') const express = require('express') const socketio = require('socket.io') const { generatemsg } = require('./utils/messages') const { addUser, removeUser, getUser, getUserInRoom } = require('./utils/users') const pool = require('./utils/dbconfig'); const db = require('../public/js/database'); const app = express() const server = http.createServer(app) const io = socketio(server) const PORT = process.env.PORT || 3000 const publicdir = path.join(__dirname, '../public') app.use(express.static(publicdir)) io.on("connection", (socket) => { console.log("new connection from socket.io") socket.on("join", ({ username, room }, cb) => { const { error, user } = addUser({ id: socket.id, username, room }) if (error) { return cb(error) } socket.join(user.room) //generate random color const colorsArray = ["red", "green", "lime", "purple", "blue"] var arrayPos = Math.floor(Math.random() * colorsArray.length) var colorTmp = colorsArray[arrayPos] //Add New User to DB var userTmp = user.username var roomTmp = user.room db.addCurrentUserToDB(userTmp, roomTmp, colorTmp) io.to(user.room).emit("roomData", { room: user.room, users: getUserInRoom(user.room) }) cb() }) socket.on("sendMessage", (msg, cb) => { const user = getUser(socket.id) io.to(user.room).emit("message", generatemsg(user.username, msg)) cb() }) socket.on("disconnect", () => { const user = removeUser(socket.id) console.log(user) if (user) { io.to(user.room).emit("message", generatemsg(`${user.username} left.`)) io.to(user.room).emit("roomData", { room: user.room, users: getUserInRoom(user.room) }) } }) }) server.listen(PORT, () => { console.log("server is up at port: " + PORT) })
我目前对服务端JS和客户端JS的边界区分比较模糊。我判断index.js是服务端JS的依据是它可以使用变量 = require('模块名')的语法。
utils目录下存放我的数据库配置文件,为了共享MySQL连接,我认为需要创建连接池,这部分的实现代码如下:
dbconfig.js
const mysql = require('mysql'); const pool = mysql.createPool({ connectionLimit : 100, host: "xxxx", port: 3306, user: "xxxx", password: "xxxx", database: 'xxxx', multipleStatements: true }); pool.getConnection(function(err, connection) { if (err) throw err; // not connected! console.log('Connected from dbconfig.js'); }); module.exports.pool = pool;
我不太理解为什么utils文件夹下的文件仍然属于服务端JS,所以我可以在index.js中引入这些文件,同时调用位于客户端database.js文件中的函数。
我把所有会在项目中随时调用的数据库操作函数都存放在public/js文件夹下。
Database.js(精简版)
//Add New User function addCurrentUserToDB(userTmp, roomTmp, colorTmp) { var sql = 'INSERT INTO pzs_info (username, roomname, usercolor) VALUES (?, ?, ?)'; pool.getConnection(function(err, connection) { if (err) throw err; // not connected! // Use the connection connection.query(sql, [userTmp, roomTmp, colorTmp], (err, result, fields) => { if (err) throw err; console.log('Added user' + userTmp); //result.changedRows connection.release(); }); }); }
现在database.js属于客户端JS,因此即使我在dbconfig.js中导出了MySQL连接,database.js也无法访问该连接,我猜测是因为连接存放在服务端JS文件中。
调整不同版本的代码后,我遇到了各类报错,包括pool不是函数、pool未定义等,我最新尝试的版本代码如下:
Database.js(最新版本)
const dbstuff = require('../../src/utils/dbconfig'); //Add New User function addCurrentUserToDB(userTmp, roomTmp, colorTmp) { var sql = 'INSERT INTO pzs_info (username, roomname, usercolor) VALUES (?, ?, ?)'; dbstuff.pool.query(sql, [userTmp, roomTmp, colorTmp], function (error, results, fields) { if (error) throw error; console.log('Added user' + userTmp); //result.changedRows }); }
我猜测报错原因是当前database.js属于客户端JS,不支持require函数。
但很奇怪的是,即使控制台输出了database.js无法使用require的报错信息,代码仍然可以正常将用户名、房间号、用户颜色写入数据库,这点我无法理解。
我想我可能完全没有掌握Node.js在客户端与服务端之间共享函数的基础逻辑。
更新:
感谢网友的建议,我引入了esm模块,现在可以混合使用require和import语法,已经可以在database.js(客户端)中引入dbconfig.js(服务端)的连接,这部分已经正常。
但我现在卡在将database.js引入同属客户端的chat.js文件的步骤。我尝试在chat.js顶部添加如下import语句:
import { updateUserList } from './database.js';
但报错SyntaxError: import declarations may only appear at top level of a module。
如果我在chat.html中给script标签添加type="module"属性,如下:
<script src="/js/chat.js" type="module"></script>
又会出现多个报错:chat.js文件404、文件因不允许的mime类型被拦截等。
我现在又陷入了困境,不清楚为什么不能直接在chat.html中引入database.js文件使用,还请大家帮忙解答,谢谢。
核心问题梳理
- 首先你混淆了服务端JS和客户端JS的边界:只要是运行在Node.js环境中的代码,都属于服务端JS,和它存放的文件夹没有必然关系。你放在
public/js下的database.js之所以能成功写入数据库,是因为你在服务端入口index.js中用require引入了它,此时这份代码是在Node环境下执行,调用的是服务端的数据库连接池;而浏览器加载public目录下的database.js时,是在客户端(浏览器)环境运行,既没有require能力,也没有数据库连接权限,所以才会出现客户端控制台报错、但数据库写入成功的现象——报错是浏览器端执行代码抛出的,写入是服务端执行同一份代码完成的,两者互不影响。 - 其次你有严重的安全风险:
public目录是express配置的静态资源目录,所有存放在这里的文件都会直接暴露给任意客户端下载,把数据库配置、操作逻辑放在这里会直接泄露你的数据库账号密码,可能导致数据被篡改、删除。 - 最后模块导入报错的本质是你走错了方向:数据库操作逻辑本来就不该在客户端运行,不需要让客户端引入
database.js。
整改方案
1. 调整文件结构
把public/js/database.js整个移动到src/utils目录下,作为纯服务端模块,不要放在公开的静态资源目录中。
2. 修正逻辑调用规则
所有数据库操作都只在服务端执行:
- 客户端如果需要触发数据库操作,通过socket.io给服务端发送自定义事件,传递必要参数
- 服务端监听对应事件,调用
utils/database.js中的方法完成数据库操作后,再通过socket.io把结果返回给客户端
3. 前端ES模块报错修复(如果确实需要前端模块化)
如果你的前端逻辑需要用ES模块语法,做两处配置即可:
- 确保html中script标签的
src路径和实际静态资源路径匹配,同时添加type="module"属性 - 在express的静态资源中间件中补充mime类型配置:
app.use(express.static(publicdir, { setHeaders: (res, filePath) => { if (filePath.endsWith('.js')) { res.setHeader('Content-Type', 'application/javascript') } } }))
内容的提问来源于stack exchange,提问作者Nick

