MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Node.js Express 集成数据库进行数据操作

2021-12-113.1k 阅读

准备工作

在开始Node.js Express集成数据库进行数据操作之前,我们需要确保已经安装了Node.js和npm(Node.js的包管理器)。同时,我们要确定使用的数据库类型,这里以MySQL为例进行讲解。

首先,创建一个新的Node.js项目目录:

mkdir node-express-db
cd node-express-db

初始化项目,这会生成一个package.json文件,用于管理项目的依赖:

npm init -y

接下来,安装Express框架和MySQL驱动:

npm install express mysql

安装完成后,在项目目录中创建一个app.js文件,这将是我们的Express应用主文件。

Express基础设置

app.js文件中,我们先进行Express的基础设置:

const express = require('express');
const app = express();
const port = 3000;

// 解析JSON和URL编码格式的请求体
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

上述代码引入了Express模块,创建了一个Express应用实例app,设置了应用监听的端口为3000,并使用express.json()express.urlencoded()中间件来处理不同格式的请求体。

连接MySQL数据库

继续在app.js中添加连接MySQL数据库的代码:

const mysql = require('mysql');

// 创建数据库连接池
const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'test_db',
    connectionLimit: 10
});

上述代码引入了mysql模块,并使用createPool方法创建了一个数据库连接池。连接池的参数包括数据库主机地址host、用户名user、密码password、数据库名database以及最大连接数connectionLimit。连接池的好处在于可以复用连接,提高数据库操作的效率,避免频繁创建和销毁连接带来的性能开销。

数据库查询操作

查询所有数据

我们以一个简单的users表为例,假设该表有idnameemail字段。现在我们来实现查询所有用户数据的功能:

app.get('/users', (req, res) => {
    pool.getConnection((err, connection) => {
        if (err) throw err;
        connection.query('SELECT * FROM users', (error, results, fields) => {
            connection.release();
            if (error) throw error;
            res.json(results);
        });
    });
});

在上述代码中,我们使用app.get方法定义了一个处理/users路径的GET请求的路由。在路由处理函数中,通过pool.getConnection获取一个数据库连接。获取连接后,使用connection.query方法执行SQL查询语句SELECT * FROM users。查询完成后,通过connection.release()将连接释放回连接池。如果查询过程中没有错误,就将查询结果以JSON格式返回给客户端。

根据条件查询数据

假设我们要根据用户的id查询单个用户的数据,可以这样实现:

app.get('/users/:id', (req, res) => {
    const id = req.params.id;
    pool.getConnection((err, connection) => {
        if (err) throw err;
        const query = 'SELECT * FROM users WHERE id =?';
        connection.query(query, [id], (error, results, fields) => {
            connection.release();
            if (error) throw error;
            if (results.length > 0) {
                res.json(results[0]);
            } else {
                res.status(404).send('User not found');
            }
        });
    });
});

这里,我们通过req.params.id获取URL参数中的id值。然后构造SQL查询语句SELECT * FROM users WHERE id =?,其中?是占位符,实际的值通过数组[id]传递给connection.query方法。如果查询到结果,就返回该用户的数据;如果没有查询到,则返回404状态码和相应的提示信息。

数据库插入操作

插入单条数据

下面实现向users表插入单条用户数据的功能:

app.post('/users', (req, res) => {
    const { name, email } = req.body;
    pool.getConnection((err, connection) => {
        if (err) throw err;
        const query = 'INSERT INTO users (name, email) VALUES (?,?)';
        connection.query(query, [name, email], (error, results, fields) => {
            connection.release();
            if (error) throw error;
            res.json({ message: 'User added successfully', id: results.insertId });
        });
    });
});

在这个app.post路由处理函数中,我们从请求体req.body中获取nameemail字段。构造SQL插入语句INSERT INTO users (name, email) VALUES (?,?),并将nameemail作为参数传递给connection.query方法。如果插入成功,返回成功信息以及新插入数据的insertId

插入多条数据

如果要插入多条用户数据,可以这样实现:

app.post('/users/batch', (req, res) => {
    const users = req.body;
    const values = [];
    users.forEach(user => {
        values.push([user.name, user.email]);
    });
    pool.getConnection((err, connection) => {
        if (err) throw err;
        const query = 'INSERT INTO users (name, email) VALUES?';
        connection.query(query, [values], (error, results, fields) => {
            connection.release();
            if (error) throw error;
            res.json({ message: 'Users added successfully', count: results.affectedRows });
        });
    });
});

这里,我们从请求体中获取一个用户数组users。然后遍历数组,将每个用户的nameemail组成一个子数组,放入values数组中。构造SQL插入语句INSERT INTO users (name, email) VALUES?,并将values数组作为参数传递给connection.query方法。如果插入成功,返回成功信息以及受影响的行数。

数据库更新操作

更新单条数据

假设我们要更新用户的email,可以这样实现:

app.put('/users/:id', (req, res) => {
    const id = req.params.id;
    const { email } = req.body;
    pool.getConnection((err, connection) => {
        if (err) throw err;
        const query = 'UPDATE users SET email =? WHERE id =?';
        connection.query(query, [email, id], (error, results, fields) => {
            connection.release();
            if (error) throw error;
            if (results.affectedRows > 0) {
                res.json({ message: 'User updated successfully' });
            } else {
                res.status(404).send('User not found');
            }
        });
    });
});

在这个app.put路由处理函数中,通过req.params.id获取要更新的用户id,从请求体中获取新的email值。构造SQL更新语句UPDATE users SET email =? WHERE id =?,并将emailid作为参数传递给connection.query方法。如果更新成功且受影响的行数大于0,返回成功信息;否则返回404状态码和相应的提示信息。

批量更新数据

如果要批量更新用户数据,假设我们根据用户的id更新多个用户的name,可以这样实现:

app.put('/users/batch', (req, res) => {
    const users = req.body;
    const updates = [];
    users.forEach(user => {
        updates.push([user.name, user.id]);
    });
    pool.getConnection((err, connection) => {
        if (err) throw err;
        const query = 'UPDATE users SET name =? WHERE id =?';
        connection.query(query, updates, (error, results, fields) => {
            connection.release();
            if (error) throw error;
            res.json({ message: 'Users updated successfully', count: results.affectedRows });
        });
    });
});

这里,我们从请求体中获取用户数组users。遍历数组,将每个用户的nameid组成一个子数组,放入updates数组中。构造SQL更新语句UPDATE users SET name =? WHERE id =?,并将updates数组作为参数传递给connection.query方法。如果更新成功,返回成功信息以及受影响的行数。

数据库删除操作

删除单条数据

要删除users表中指定id的用户数据,可以这样实现:

app.delete('/users/:id', (req, res) => {
    const id = req.params.id;
    pool.getConnection((err, connection) => {
        if (err) throw err;
        const query = 'DELETE FROM users WHERE id =?';
        connection.query(query, [id], (error, results, fields) => {
            connection.release();
            if (error) throw error;
            if (results.affectedRows > 0) {
                res.json({ message: 'User deleted successfully' });
            } else {
                res.status(404).send('User not found');
            }
        });
    });
});

在这个app.delete路由处理函数中,通过req.params.id获取要删除的用户id。构造SQL删除语句DELETE FROM users WHERE id =?,并将id作为参数传递给connection.query方法。如果删除成功且受影响的行数大于0,返回成功信息;否则返回404状态码和相应的提示信息。

批量删除数据

如果要批量删除用户数据,假设我们根据用户的id数组删除多个用户,可以这样实现:

app.delete('/users/batch', (req, res) => {
    const ids = req.body.ids;
    const values = [];
    ids.forEach(id => {
        values.push([id]);
    });
    pool.getConnection((err, connection) => {
        if (err) throw err;
        const query = 'DELETE FROM users WHERE id =?';
        connection.query(query, values, (error, results, fields) => {
            connection.release();
            if (error) throw error;
            res.json({ message: 'Users deleted successfully', count: results.affectedRows });
        });
    });
});

这里,我们从请求体中获取ids数组,该数组包含要删除的用户id。遍历ids数组,将每个id组成一个子数组,放入values数组中。构造SQL删除语句DELETE FROM users WHERE id =?,并将values数组作为参数传递给connection.query方法。如果删除成功,返回成功信息以及受影响的行数。

错误处理优化

在前面的代码中,我们对数据库操作的错误处理相对简单,只是简单地抛出错误。在实际应用中,我们需要更友好的错误处理机制,以便给客户端返回合适的错误信息。

app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Something went wrong!');
});

上述代码添加了一个全局错误处理中间件。当数据库操作或其他路由处理过程中抛出错误时,该中间件会捕获错误,在控制台打印错误堆栈信息,并向客户端返回500状态码和错误提示信息。这样可以避免因为未处理的错误导致应用崩溃,同时给客户端提供一个统一的错误反馈。

事务处理

在某些情况下,我们需要确保多个数据库操作要么全部成功,要么全部失败,这就需要使用事务处理。例如,我们有一个转账操作,从一个账户扣除金额,同时向另一个账户增加金额,这两个操作必须作为一个事务来处理。

app.post('/transfer', (req, res) => {
    const { fromAccount, toAccount, amount } = req.body;
    pool.getConnection((err, connection) => {
        if (err) throw err;
        connection.beginTransaction((err) => {
            if (err) throw err;
            const subtractQuery = 'UPDATE accounts SET balance = balance -? WHERE account_id =?';
            const addQuery = 'UPDATE accounts SET balance = balance +? WHERE account_id =?';
            connection.query(subtractQuery, [amount, fromAccount], (error, results, fields) => {
                if (error) {
                    connection.rollback(() => {
                        throw error;
                    });
                }
                connection.query(addQuery, [amount, toAccount], (error, results, fields) => {
                    if (error) {
                        connection.rollback(() => {
                            throw error;
                        });
                    }
                    connection.commit((err) => {
                        if (err) throw err;
                        res.json({ message: 'Transfer successful' });
                    });
                });
            });
        });
        connection.release();
    });
});

在上述代码中,我们定义了一个/transfer路由来处理转账操作。首先获取数据库连接并开始一个事务beginTransaction。然后执行从fromAccount扣除金额的SQL操作,如果该操作失败,通过rollback回滚事务。如果扣除操作成功,继续执行向toAccount增加金额的SQL操作,若此操作失败同样回滚事务。若两个操作都成功,则通过commit提交事务,并返回成功信息。最后,无论事务执行结果如何,都要释放数据库连接。

连接池的优化与管理

在实际应用中,连接池的性能和稳定性至关重要。我们可以通过以下几种方式对连接池进行优化:

  1. 调整连接池参数:根据应用的负载情况,合理调整connectionLimit参数。如果并发请求量较大,可以适当增加连接数;如果应用负载较低,可以减少连接数,以避免资源浪费。
  2. 连接池监控:可以使用一些工具来监控连接池的使用情况,如连接的活跃数、等待数等。这样可以及时发现连接池是否出现瓶颈,以便做出相应的调整。
  3. 连接的健康检查:定期对连接池中的连接进行健康检查,确保连接是可用的。对于不可用的连接,及时从连接池中移除并重新创建新的连接。例如,可以在获取连接时进行简单的测试查询:
pool.getConnection((err, connection) => {
    if (err) throw err;
    connection.query('SELECT 1', (error, results, fields) => {
        if (error) {
            // 处理连接不可用的情况,如重新获取连接
            connection.destroy();
            pool.getConnection((err, newConnection) => {
                // 使用新的连接进行后续操作
            });
        } else {
            // 连接可用,进行正常的数据库操作
        }
    });
});

通过以上方式,可以提高连接池的可靠性和性能,从而提升整个应用的数据库操作效率。

数据库安全性考虑

在进行数据库操作时,安全性是不容忽视的。以下是一些常见的数据库安全措施:

  1. 防止SQL注入:在前面的代码中,我们使用了占位符(?)来传递参数,这是防止SQL注入的一种有效方式。如果直接将用户输入拼接到SQL语句中,就可能导致SQL注入攻击。例如,以下是错误的做法:
// 错误示例,存在SQL注入风险
const id = req.params.id;
const query = 'SELECT * FROM users WHERE id ='+ id;
connection.query(query, (error, results, fields) => {
    //...
});

而正确的做法是使用占位符:

const id = req.params.id;
const query = 'SELECT * FROM users WHERE id =?';
connection.query(query, [id], (error, results, fields) => {
    //...
});
  1. 用户认证与授权:确保只有经过认证和授权的用户才能进行数据库操作。在Express应用中,可以使用中间件来验证用户的身份和权限。例如,使用JSON Web Tokens(JWT)进行用户认证:
const jwt = require('jsonwebtoken');

// 验证JWT的中间件
const authenticateJwt = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];
    if (token == null) return res.sendStatus(401);
    jwt.verify(token, 'your-secret-key', (err, user) => {
        if (err) return res.sendStatus(403);
        req.user = user;
        next();
    });
};

// 需要认证的路由
app.get('/protected/users', authenticateJwt, (req, res) => {
    // 只有认证通过的用户才能访问此路由,进行数据库操作
});
  1. 数据库密码管理:妥善保管数据库的用户名和密码,不要将密码硬编码在代码中。可以使用环境变量来存储密码,例如在.env文件中定义:
DB_PASSWORD=your-database-password

然后在Node.js应用中使用dotenv模块来加载环境变量:

const dotenv = require('dotenv');
dotenv.config();

const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: process.env.DB_PASSWORD,
    database: 'test_db',
    connectionLimit: 10
});

通过以上安全措施,可以有效提高数据库操作的安全性,保护应用的数据免受攻击。

与其他数据库的集成

虽然我们以MySQL为例进行了详细讲解,但Node.js Express也可以与其他数据库进行集成,如MongoDB、PostgreSQL等。

集成MongoDB

  1. 安装依赖:
npm install express mongoose
  1. 连接MongoDB:
const express = require('express');
const mongoose = require('mongoose');
const app = express();
const port = 3000;

mongoose.connect('mongodb://localhost:27017/test_db', {
    useNewUrlParser: true,
    useUnifiedTopology: true
});

const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', () => {
    console.log('Connected to MongoDB');
});
  1. 定义数据模型和进行数据操作:
const userSchema = new mongoose.Schema({
    name: String,
    email: String
});

const User = mongoose.model('User', userSchema);

app.get('/users', async (req, res) => {
    try {
        const users = await User.find();
        res.json(users);
    } catch (error) {
        res.status(500).send(error.message);
    }
});

集成PostgreSQL

  1. 安装依赖:
npm install express pg
  1. 连接PostgreSQL:
const { Pool } = require('pg');
const express = require('express');
const app = express();
const port = 3000;

const pool = new Pool({
    user: 'your-username',
    host: 'localhost',
    database: 'test_db',
    password: 'your-password',
    port: 5432,
});
  1. 进行数据操作:
app.get('/users', (req, res) => {
    pool.query('SELECT * FROM users', (error, results) => {
        if (error) {
            console.error(error);
            res.status(500).send('Error fetching users');
        } else {
            res.json(results.rows);
        }
    });
});

通过以上示例可以看出,虽然不同数据库的连接方式和数据操作语法有所不同,但在Node.js Express应用中集成它们的基本思路是相似的,都是先安装相应的数据库驱动,然后进行连接设置,最后执行各种数据操作。

通过以上对Node.js Express集成数据库进行数据操作的详细讲解,涵盖了从基础设置到复杂事务处理、性能优化以及安全性等多方面的内容,希望能帮助开发者在实际项目中更好地实现与数据库的交互。