Express连接MySQL实现CRUD并与模板页面数据交互全指南

文章目录

Express连接MySQL实现CRUD并与模板页面数据交互全指南

在Express应用中,连接MySQL数据库并实现增删改查(CRUD)操作,同时与模板页面进行数据交互,是构建动态Web应用的核心流程。本文将详细介绍从数据库连接、路由设计、CRUD实现到模板数据传递的完整方案,帮助开发者掌握前后端数据交互的关键技术。

一、环境准备与依赖安装

1. 核心依赖

  • express:Web框架
  • mysql2:MySQL客户端(支持Promise和连接池,性能优于旧版mysql
  • ejs:模板引擎(用于数据渲染,也可替换为Pug等)
  • body-parser:解析表单提交数据(Express 4.16+已内置,可直接使用)

2. 安装命令

npm install express mysql2 ejs

二、MySQL连接配置

使用连接池(Connection Pool)管理数据库连接,提升性能和并发处理能力:

const mysql = require('mysql2/promise');

// 数据库配置
const dbConfig = {
  host: 'localhost',       // 数据库地址
  user: 'root',            // 用户名
  password: 'your_password', // 密码
  database: 'test_db',     // 数据库名
  port: 3306,              // 端口(默认3306)
  waitForConnections: true,
  connectionLimit: 10,     // 最大连接数
  queueLimit: 0
};

// 创建连接池
const pool = mysql.createPool(dbConfig);

// 测试连接
async function testConnection() {
  try {
    const connection = await pool.getConnection();
    console.log('数据库连接成功');
    connection.release(); // 释放连接回池
  } catch (err) {
    console.error('数据库连接失败:', err.message);
  }
}

// 执行SQL查询的通用方法
async function query(sql, params = []) {
  try {
    const [results] = await pool.execute(sql, params);
    return results;
  } catch (err) {
    console.error('SQL执行错误:', err.message);
    throw err; // 抛出错误供上层处理
  }
}

module.exports = {
  query,
  testConnection
};

三、Express应用初始化与路由设计

1. 应用入口文件(app.js

配置模板引擎、静态资源和路由:

const express = require('express');
const path = require('path');
const { testConnection } = require('./db');
const userRoutes = require('./routes/users');

// 初始化Express
const app = express();
const port = 3000;

// 配置模板引擎(EJS)
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views')); // 模板文件目录

// 解析表单数据(POST请求)
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

// 静态资源目录(CSS、JS、图片等)
app.use(express.static(path.join(__dirname, 'public')));

// 测试数据库连接
testConnection();

// 注册路由
app.use('/users', userRoutes); // 用户相关CRUD路由
app.get('/', (req, res) => {
  res.redirect('/users'); // 根路径重定向到用户列表
});

// 启动服务器
app.listen(port, () => {
  console.log(`服务器运行在 http://localhost:${port}`);
});

2. 数据库表设计(示例)

创建users表存储用户数据:

CREATE TABLE IF NOT EXISTS users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  email VARCHAR(100) UNIQUE NOT NULL,
  age INT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

四、CRUD操作实现与模板数据交互

以用户管理为例,实现完整的增删改查,并在模板中展示和交互数据。

1. 路由处理(routes/users.js

const express = require('express');
const router = express.Router();
const db = require('../db');

// 1. 查:获取所有用户并渲染列表页
router.get('/', async (req, res) => {
    try {
        const users = await db.query('SELECT * FROM users ORDER BY created_at DESC');
        // 传递数据到模板(users变量将在EJS中使用)
        res.render('users/list', {
            title: '用户列表',
            users: users,
            message: req.query.message // 用于显示操作结果提示
        });
    } catch (err) {
        res.status(500).send('获取用户失败:' + err.message);
    }
});

// 2. 查:获取单个用户详情
router.get('/:id', async (req, res) => {
    try {
        const [users] = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
        if (users.length === 0) {
            return res.status(404).send('用户不存在');
        }
        res.render('users/detail', {
            title: '用户详情',
            user: users[0]
        });
    } catch (err) {
        res.status(500).send('获取用户详情失败:' + err.message);
    }
});

// 3. 跳转到添加用户表单页
router.get('/add', (req, res) => {
    res.render('users/form', {
        title: '添加用户',
        user: {}, // 空对象,保持表单字段一致性
        action: '/users/add' // 表单提交地址
    });
});

// 4. 增:提交添加用户表单
router.post('/add', async (req, res) => {
    try {
        const {name, email, age} = req.body;
        // 插入数据(使用参数化查询防SQL注入)
        await db.query(
            'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
            [name, email, age || null]
        );
        // 重定向到列表页并携带成功消息
        res.redirect('/users?message=用户添加成功');
    } catch (err) {
        res.status(500).send('添加用户失败:' + err.message);
    }
});

// 5. 跳转到编辑用户表单页
router.get('/edit/:id', async (req, res) => {
    try {
        const [users] = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
        if (users.length === 0) {
            return res.status(404).send('用户不存在');
        }
        res.render('users/form', {
            title: '编辑用户',
            user: users[0],
            action: `/users/edit/${req.params.id}`
        });
    } catch (err) {
        res.status(500).send('获取编辑数据失败:' + err.message);
    }
});

// 6. 改:提交编辑用户表单
router.post('/edit/:id', async (req, res) => {
    try {
        const {name, email, age} = req.body;
        await db.query(
            'UPDATE users SET name = ?, email = ?, age = ? WHERE id = ?',
            [name, email, age || null, req.params.id]
        );
        res.redirect('/users?message=用户更新成功');
    } catch (err) {
        res.status(500).send('更新用户失败:' + err.message);
    }
});

// 7. 删:删除用户
router.get('/delete/:id', async (req, res) => {
    try {
        await db.query('DELETE FROM users WHERE id = ?', [req.params.id]);
        res.redirect('/users?message=用户删除成功');
    } catch (err) {
        res.status(500).send('删除用户失败:' + err.message);
    }
});

module.exports = router;

2. 模板页面实现(EJS)

(1)用户列表页(views/users/list.ejs

展示用户列表并提供操作入口:

<% include ../partials/header %>

<div class="container">
  <h1><%= title %></h1>
  
  <!-- 操作结果提示 -->
  <% if (message) { %>
    <div class="alert-success"><%= message %></div>
  <% } %>

  <!-- 添加用户按钮 -->
  <a href="/users/add" class="btn-add">添加新用户</a>

  <!-- 用户列表表格 -->
  <table class="user-table">
    <thead>
      <tr>
        <th>ID</th>
        <th>姓名</th>
        <th>邮箱</th>
        <th>年龄</th>
        <th>创建时间</th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody>
      <% if (users.length > 0) { %>
        <% users.forEach(user => { %>
          <tr>
            <td><%= user.id %></td>
            <td><%= user.name %></td>
            <td><%= user.email %></td>
            <td><%= user.age || '未填写' %></td>
            <td><%= new Date(user.created_at).toLocaleString() %></td>
            <td class="actions">
              <a href="/users/<%= user.id %>" class="btn-view">查看</a>
              <a href="/users/edit/<%= user.id %>" class="btn-edit">编辑</a>
              <a href="/users/delete/<%= user.id %>" class="btn-delete" 
                 onclick="return confirm('确定要删除吗?')">删除</a>
            </td>
          </tr>
        <% }) %>
      <% } else { %>
        <tr>
          <td colspan="6" class="no-data">暂无用户数据</td>
        </tr>
      <% } %>
    </tbody>
  </table>
</div>

<% include ../partials/footer %>
(2)用户表单页(views/users/form.ejs

用于添加和编辑用户(复用同一表单):

<% include ../partials/header %>

<div class="container">
  <h1><%= title %></h1>
  
  <!-- 表单:提交到指定action(添加/编辑) -->
  <form action="<%= action %>" method="POST" class="user-form">
    <div class="form-group">
      <label for="name">姓名:</label>
      <input type="text" id="name" name="name" 
             value="<%= user.name || '' %>" required>
    </div>
    
    <div class="form-group">
      <label for="email">邮箱:</label>
      <input type="email" id="email" name="email" 
             value="<%= user.email || '' %>" required>
    </div>
    
    <div class="form-group">
      <label for="age">年龄:</label>
      <input type="number" id="age" name="age" 
             value="<%= user.age || '' %>" min="0">
    </div>
    
    <div class="form-actions">
      <button type="submit" class="btn-save">保存</button>
      <a href="/users" class="btn-cancel">取消</a>
    </div>
  </form>
</div>

<% include ../partials/footer %>
(3)用户详情页(views/users/detail.ejs

展示单个用户的详细信息:

<% include ../partials/header %>

<div class="container">
  <h1><%= title %></h1>
  
  <div class="user-detail">
    <p><strong>ID:</strong><%= user.id %></p>
    <p><strong>姓名:</strong><%= user.name %></p>
    <p><strong>邮箱:</strong><%= user.email %></p>
    <p><strong>年龄:</strong><%= user.age || '未填写' %></p>
    <p><strong>创建时间:</strong><%= new Date(user.created_at).toLocaleString() %></p>
  </div>
  
  <div class="detail-actions">
    <a href="/users/edit/<%= user.id %>" class="btn-edit">编辑</a>
    <a href="/users" class="btn-back">返回列表</a>
  </div>
</div>

<% include ../partials/footer %>
(4)公共模板组件(示例:views/partials/header.ejs
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= title %> - 用户管理系统</title>
  <link rel="stylesheet" href="/css/style.css">
</head>
<body>

五、数据交互核心逻辑解析

1. 后端向前端(模板)传递数据

通过res.render(template, data)方法将数据传递到模板:

// 传递用户列表和标题
res.render('users/list', { 
  title: '用户列表', 
  users: users, 
  message: '操作成功' 
});

在EJS模板中通过<%= 变量名 %>使用数据:

<h1><%= title %></h1>
<% users.forEach(user => { %>
  <p><%= user.name %></p>
<% }) %>

2. 前端(表单)向后端传递数据

  • 表单提交:通过method="POST"将数据发送到后端路由,后端通过req.body获取:
    <form action="/users/add" method="POST">
      <input name="name" value="张三">
    </form>
    
  • URL参数:通过/users/:id传递路径参数,后端通过req.params.id获取。
  • 查询字符串:通过/users?message=success传递,后端通过req.query.message获取(常用于跳转时传递提示信息)。

3. CRUD操作与页面跳转流程

  • 查询(Read):直接渲染模板并传递数据库查询结果。
  • 添加/编辑(Create/Update)
    1. 先渲染表单页(空表单或预填数据)。
    2. 表单提交后,后端处理数据并执行数据库操作。
    3. 操作完成后通过res.redirect跳转到列表页,并携带结果提示。
  • 删除(Delete):点击删除按钮后,后端执行删除操作,再重定向到列表页。

六、注意事项

1. 防SQL注入

始终使用参数化查询?占位符),避免直接拼接SQL字符串:

// 安全(参数化查询)
db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);

// 危险(可能导致SQL注入)
db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);

2. 错误处理

  • 数据库操作必须用try/catch捕获错误,避免应用崩溃。
  • 对用户输入进行验证(如邮箱格式、年龄范围),可使用joi等验证库。

3. 连接池管理

  • 无需手动关闭连接,使用pool.execute后连接会自动释放回池。
  • 根据服务器性能调整connectionLimit(建议10-50)。

4. 模板数据安全

  • 使用<%= 变量 %>会自动转义HTML,防止XSS攻击(如用户输入包含<script>标签)。
  • 若需显示原始HTML(如富文本),使用<%- 变量 %>,但需确保内容安全。

七、总结

Express连接MySQL并与模板交互的核心流程可概括为:

  1. 数据库层:通过连接池管理MySQL连接,提供通用查询方法。
  2. 路由层:处理HTTP请求,调用数据库方法执行CRUD操作,通过res.render传递数据到模板。
  3. 视图层:使用EJS模板渲染数据,通过表单和链接与后端交互。

关键技术点包括:参数化查询防注入、连接池提升性能、res.render数据传递、表单提交与重定向。掌握这些技术,可构建安全、高效的动态Web应用,实现数据的完整生命周期管理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值