# node-express-sequelize-mysql **Repository Path**: fe521/node-express-sequelize-mysql ## Basic Information - **Project Name**: node-express-sequelize-mysql - **Description**: 使用node-express-sequelize-mysql技术实现增删改查的一个例子 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-07-26 - **Last Updated**: 2023-07-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # node-express-sequelize-mysql ## 快速开始 ### 1. 启动 mysql 服务器 让我们先通过 Docker 启动一个 MySQL 服务器: ```bash docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 -d mysql:8 ``` 这会启动一个名为 "some-mysql" 的 MySQL 容器,密码设置为 "my-secret-pw"。使用你需要的任何密码替换 "my-secret-pw"。 ### 2. 启动项目 ```bash npm start ``` 访问: http://localhost:3000 ## 开发日志 我们将添加一个`Profile`模型,并将`User`模型与`Profile`模型相关联。 首先,我们需要安装需要的包: ```bash mkdir node-express-sequelize cd node-express-sequelize npm init -y npm install express sequelize mysql2 ``` 然后,创建 `app.js` 文件,添加以下内容: ```js const express = require("express"); const { Sequelize, DataTypes, Model } = require("sequelize"); const app = express(); app.use(express.json()); app.use(express.static("public")); const sequelize = new Sequelize("test", "testuser", "testpassword", { host: "localhost", dialect: "mysql", port: 3306, dialectOptions: { dateStrings: true, typeCast: true, timezone: "+08:00", }, timezone: "+08:00", //for writing to database logging: true, // disable logging }); class User extends Model {} User.init( { uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true, }, username: { type: DataTypes.STRING, allowNull: false, unique: true, }, email: { type: DataTypes.STRING, allowNull: false, unique: true, validate: { isEmail: true, }, }, password: { type: DataTypes.STRING, allowNull: false, }, isAdmin: { type: DataTypes.BOOLEAN, defaultValue: false, }, birthday: { type: DataTypes.DATEONLY, allowNull: true, }, credit: { type: DataTypes.DECIMAL(10, 2), allowNull: false, defaultValue: 0.0, }, status: { type: DataTypes.ENUM("active", "inactive"), defaultValue: "active", }, }, { sequelize, modelName: "User", // paranoid 是一个模型选项,用于控制软删除(也称为逻辑删除)功能。 // 当 paranoid 被设置为 true 时,Sequelize 将会自动为模型启用软删除特性。 // 软删除是一种在数据库中标记数据为已删除状态而非实际物理删除的技术 // paranoid: true, timestamps: true, } ); class Profile extends Model {} Profile.init( { userId: { type: DataTypes.UUID, references: { model: User, key: "uuid", }, }, fullName: { type: DataTypes.STRING, allowNull: false, }, bio: DataTypes.TEXT, avatar: DataTypes.BLOB, }, { sequelize, modelName: "Profile", } ); User.hasOne(Profile, { foreignKey: "userId", // Add the foreign key option onDelete: "CASCADE", onUpdate: "CASCADE", }); Profile.belongsTo(User, { foreignKey: "userId", // Add the foreign key option onDelete: "CASCADE", // Add the onDelete option }); sequelize.sync(); app.post("/users", async function (req, res) { try { console.log("req.body", req.body); const { username, email, password, fullName, bio } = req.body; const user = await User.create( { username, email, password, credit: 0.0, // Set a default value for the credit field Profile: { fullName, bio, }, }, { include: [Profile], } ); res.json(user); } catch (error) { console.error("Error creating user:", error.message); console.error("Validation errors:", error.errors); // Prepare validation errors for client let clientErrors = error.errors.map((err) => { return { message: err.message, type: err.type, path: err.path, value: err.value, }; }); res .status(500) .json({ error: "Error creating user", validationErrors: clientErrors }); } }); app.get("/users", async function (req, res) { const users = await User.findAll({ include: [Profile], // Include the associated Profile model }); // Map the results to include the fullName property from the associated Profile const mappedUsers = users.map((user) => { return { uuid: user.uuid, username: user.username, email: user.email, fullName: user.Profile ? user.Profile.fullName : null, bio: user.Profile ? user.Profile.bio : null, }; }); console.log("Users count:", users.length); console.log("Mapped users count:", mappedUsers.length); res.json(mappedUsers); }); app.put("/users/:uuid", async function (req, res) { try { const { username, email, password, fullName, bio } = req.body; const user = await User.findByPk(req.params.uuid, { include: [Profile], }); if (user) { // 更新 User 模型的字段 user.username = username; user.email = email; // user.password = password; // 如果 Profile 存在,则更新其字段 if (user.Profile) { user.Profile.fullName = fullName; user.Profile.bio = bio; await user.Profile.save(); } else { // 如果 Profile 不存在,则创建新的 Profile 记录并关联到 User const profile = await Profile.create({ fullName, bio, userId: user.uuid, }); user.Profile = profile; } // 保存 User 模型的更改 await user.save(); res.json(user); } else { res.status(404).json({ error: "User not found" }); } } catch (error) { console.error("Error updating user:", error.message); console.error("Validation errors:", error.errors); // 准备验证错误信息发送给客户端 let clientErrors = error.errors.map((err) => { return { message: err.message, type: err.type, path: err.path, value: err.value, }; }); res .status(500) .json({ error: "Error updating user", validationErrors: clientErrors }); } }); app.delete("/users/:uuid", async function (req, res) { try { console.log("Deleting user with uuid:", req.params.uuid); const user = await User.findByPk(req.params.uuid); if (!user) { return res.status(404).json({ error: "User not found" }); } // Delete the associated profile first const profile = await Profile.findOne({ where: { userId: user.uuid } }); if (profile) { await profile.destroy(); } // Now, delete the user await user.destroy(); console.log("User deleted successfully:", user.uuid); res.json({ success: true }); } catch (error) { console.error("Error deleting user:", error.message); res.status(500).json({ error: "Error deleting user" }); } }); app.listen(3000, function () { console.log("App running on port 3000"); }); ``` 这个示例包含了多个数据类型以及相关联的表的处理。这样的模型包含了很多实际情况中可能会用到的数据类型以及它们的配置选项。然而,根据你的具体需求,你可能需要调整这些字段和选项。 ## 创建.gitignore ```bash # windows ni .gitignore touch .gitignore ``` ```bash npm i nodemon ``` package.json ```json "main": "app.js", "scripts": { "start": "nodemon app.js" }, ``` ```bash npm start ``` ```bash [nodemon] starting `node app.js` App running on port 3000 (node:12596) UnhandledPromiseRejectionWarning: SequelizeConnectionRefusedError: connect ECONNREFUSED 127.0.0.1:3306 ejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1) (node:12596) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. ``` ```bash docker stop 957d460ab6a2 ``` 你也可以设置一个新的 MySQL 用户,并为它分配一个密码。例如,你可以设置一个名为"testuser"的新用户,并把它的密码设置为"testpassword": ```bash docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -e MYSQL_DATABASE=test -e MYSQL_USER=testuser -e MYSQL_PASSWORD=testpassword -p 3306:3306 -d mysql:8 --default-authentication-plugin=mysql_native_password ``` 然后在你的 Sequelize 配置中,你应该使用新用户的名字和密码,而不是 root 用户: ```js const sequelize = new Sequelize("test", "testuser", "testpassword", { host: "localhost", dialect: "mysql", port: 3306, dialectOptions: { dateStrings: true, typeCast: true, timezone: "+08:00", }, timezone: "+08:00", //for writing to database }); ``` ```bash [nodemon] starting `node app.js` App running on port 3000 Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'Users' AND TABLE_SCHEMA = 'test' Executing (default): SHOW INDEX FROM `Users` Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'Profiles' AND TABLE_SCHEMA = 'test' Executing (default): SHOW INDEX FROM `Profiles` ``` 根据你提供的日志,没有任何错误信息,看起来你的应用已经正常启动,并成功连接到了 MySQL 数据库。 日志中的 Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'Users' AND TABLE_SCHEMA = 'test' 这样的信息是 Sequelize 在检查你的数据库中是否存在指定的数据表。如果数据表不存在,Sequelize 将会自动创建它们。 Executing (default): SHOW INDEX FROM 'Users' 和 Executing (default): SHOW INDEX FROM 'Profiles' 这样的信息表示 Sequelize 在检查这些表的索引。 现在,你的应用应该已经可以处理来自客户端的 HTTP 请求了。你可以尝试发送一些请求来测试你的增删改查(CRUD)操作是否正常工作。例如,你可以使用 Postman、curl 或者其他你喜欢的 HTTP 客户端工具来发送请求。 ## 如何不提示 Executing ```powershell PS C:\Users\Administrator\Desktop\node-express-sequelize> npm start > node-express-sequelize@1.0.0 start C:\Users\Administrator\Desktop\node-express-sequelize> nodemon app.js [nodemon] 3.0.1 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,cjs,json [nodemon] starting `node app.js` App running on port 3000 Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'Users' AND TABLE_SCHEMA = 'test' Executing (default): SHOW INDEX FROM `Users` Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'Profiles' AND TABLE_SCHEMA = 'test' Executing (default): SHOW INDEX FROM `Profiles` ``` `Executing...` 这些提示信息是由 Sequelize 的日志函数产生的,用于输出执行的 SQL 查询语句。如果你不想在控制台看到这些日志信息,你可以在创建 Sequelize 实例时将 logging 选项设置为 `false`。 这是修改后的 Sequelize 连接代码: ```javascript const sequelize = new Sequelize("test", "testuser", "testpassword", { host: "localhost", dialect: "mysql", port: 3306, dialectOptions: { dateStrings: true, typeCast: true, timezone: "+08:00", }, timezone: "+08:00", //for writing to database logging: false, // disable logging }); ``` 在这段代码中,我们在 Sequelize 的配置对象中添加了 `logging: false`,这样 Sequelize 就不会在控制台输出执行的 SQL 语句了。如果你需要在某些情况下打开日志,例如在开发环境,你也可以动态设置这个选项。 ## docker 操作 MySQL 总结上述 Docker 操作 MySQL 数据的步骤如下: 1. **启动 MySQL 容器:** 使用 Docker 命令启动 MySQL 容器,例如: ``` docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 -d mysql:latest ``` 这将在 Docker 中运行一个 MySQL 容器,创建一个名为`some-mysql`的容器,设置 MySQL 的 root 密码为`my-secret-pw`,并将主机的 3306 端口映射到容器的 3306 端口。 2. **连接到 MySQL 容器:** 使用以下命令连接到正在运行的 MySQL 容器: ``` docker exec -it some-mysql mysql -u root -p ``` 输入 MySQL root 用户的密码(在此例中为`my-secret-pw`),以便连接到 MySQL 交互式命令行。 3. **查看数据库列表:** 在 MySQL 交互式命令行中,使用以下命令查看所有数据库的列表: ``` SHOW DATABASES; ``` 这将列出 MySQL 服务器中所有数据库的名称。 4. **选择数据库(可选):** 如果需要查看特定数据库的表,可以选择该数据库。使用`USE`命令选择数据库,例如: ``` USE your_database_name; ``` 将`your_database_name`替换为您想要查看的数据库名称。 5. **查看表列表:** 在选择了数据库后,使用以下命令查看选定数据库中所有表的列表: ``` SHOW TABLES; ``` 这将显示选定数据库中所有表的名称。 6. **查看表数据:** 使用`SELECT`语句查看表中的数据。例如,如果要查看名为`your_table_name`的表中的所有数据: ``` SELECT * FROM your_table_name; ``` 这将返回表`your_table_name`中的所有行数据。 通过按照上述步骤进行操作,您将能够在 Docker 中启动 MySQL 容器,并连接到 MySQL 交互式命令行,查看数据库列表,选择特定数据库,并查看表列表以及表中的数据。 ## 还是一样无法真正删除数据中的数据,并且也没有报错 如果删除操作没有报错,但数据库中的数据仍然存在,并且您已经确认数据库连接和权限都正常,那么可能是由于 Sequelize 的软删除特性造成的。在您的代码中,您在定义模型时设置了 `paranoid: true`,这将启用 Sequelize 的软删除功能。 软删除是指在执行删除操作时,并不会真正从数据库中删除记录,而是将记录的 `deletedAt` 字段设置为当前时间,标记为已删除状态。这样设计的目的是为了保留删除记录的历史,并在需要时能够还原或恢复数据。 如果您想彻底从数据库中删除数据而不保留软删除功能,可以将模型定义中的 `paranoid: true` 改为 `paranoid: false`,或者干脆不指定 `paranoid` 参数,默认值为 `false`,如下所示: ```javascript User.init( { // 其他字段定义... }, { sequelize, modelName: "User", // paranoid: false, // 或者删除该行代码 timestamps: true, } ); ``` 修改后,再执行删除操作时,数据库中的数据应该会被彻底删除,而不是进行软删除。请注意,修改模型定义后,可能需要重新同步数据库表结构,可以通过 Sequelize 提供的 `sync` 方法来实现。 如果您希望保留软删除功能,并且希望通过接口查询时不返回软删除的数据,可以在查询时加入条件来过滤掉已删除的数据,例如: ```javascript app.get("/users", async function (req, res) { const users = await User.findAll({ include: [Profile], where: { deletedAt: null, // 过滤掉已删除的数据 }, }); // ...剩余的代码... }); ``` 希望这些解释和建议能帮助您解决问题。如果问题仍然存在,请提供更多细节或代码段,我将尽力提供进一步的帮助。