Node.js链接MySql

前言:

在现代 Web 开发和后端服务中,Node.js 因其高性能和异步特性被广泛使用。MySQL 作为流行的关系型数据库之一,提供了稳定高效的数据存储和管理能力。将 Node.js 与 MySQL 结合,可以构建强大的数据驱动型应用。

一、环境准备

安装必要依赖

bash 复制代码
npm install express mysql cors jsonwebtoken body-parser

数据库准备

创建一个名为 demo 的数据库,并添加 user 表:

sql 复制代码
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

二、核心代码实现

数据库连接配置 (dbconfig.js)

javascript 复制代码
const mysql = require('mysql');

// 建立连接池
const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: 'root',
    database: 'demo',
})

/**
 * 执行SQL查询的Promise封装
 * @param {string} sql SQL语句
 * @param {array} values 参数值
 * @returns {Promise} 查询结果
 */
const query = (sql, values) => {
    return new Promise((resolve, reject) => {
        pool.getConnection((err, connection) => {
            if (err) {
                console.log('数据库连接错误', err)
                reject(err)
                return
            }
            connection.query(sql, values, (err, rows) => {
                if (err) {
                    console.log('SQL执行错误', err)
                    reject(err)
                    return
                }
                resolve(rows)
                connection.release() // 释放连接回连接池
            })
        })
    })
}

module.exports = query;

关键点说明:

  • 使用链接池提高数据库性能,方便后期的统一管理。
  • Promise 封装使异步操作更易管理。

Express 服务器配置 (server.js)

javascript 复制代码
const express = require('express')
const query = require('./utils/dbconfig')
const cors = require('cors')
const jwt = require('jsonwebtoken')
const bodyParser = require('body-parser')

const app = express()
const PORT = 3000;

// 安全配置
const JWT_SECRET = 'your-secret-key-here'; // 生产环境应使用环境变量

// CORS跨域配置
const corsOptions = {
    origin: ['http://localhost:5173'], // 允许的前端地址
    methods: ['GET', 'POST', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization']
}

// 中间件
app.use(cors(corsOptions))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

// 启动服务器
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`)
})

到这里基本的配置就完成了,现在就可以去完成接口的实现了。

接口实现

登录接口:

javascript 复制代码
app.post('/login', async (req, res) => {
    try {
        const { username, password } = req.body;
        
        // 参数验证 对请求体中的username和password进行非空检查
        if (!username || !password) {
            return res.status(400).json({
                success: false,
                message: '用户名和密码不能为空'
            });
        }

        // 数据库查询验证 使用预处理SQL语句查询匹配的用户
        const sql = 'SELECT * FROM user WHERE username = ? AND password = ?';
        const users = await query(sql, [username, password]);
        
        if (users.length === 0) {
            return res.status(401).json({
                success: false,
                message: '用户名或密码错误'
            });
        }

        const user = users[0];

        // 使用jsonwebtoken生成访问令牌
        const token = jwt.sign(
            { userId: user.id, username: user.username },
            JWT_SECRET,
            { expiresIn: '1h' } // Token有效期1小时
        );

        // 返回成功响应
        res.json({
            success: true,
            message: '登录成功',
            token: token,
            user: {
                id: user.id,
                username: user.username
            }
        });
    } catch (err) {
        console.error('登录错误:', err);
        res.status(500).json({
            success: false,
            message: '服务器错误'
        });
    }
});

使用Express框架处理POST请求。主要功能包括参数验证、数据库查询、JWT生成和错误处理。

这边其实在实际项目中像密码这类敏感数据为了安全应该使用哈希存储而非明文查询。

javascript 复制代码
const sql = 'SELECT * FROM user WHERE username = ?';
const users = await query(sql, [username]);
const isValid = await bcrypt.compare(password, users[0].password_hash);
验证:

到这里就可以来验证我们完成的接口了,这里可以给大家推荐一个VScode中的插件:

可以看到接口是可以正常运行的,并返回了一个token给我们,这边我写了一个vue的例子,包含了 Vue Routeraxios的二次封装,这些我在之前的博客都详细的讲过,可以点击对应的文章查看我之前的博客。

我做了一个简单的登录页面:

html 复制代码
<template>
    <div class="login">
        <h1>Login</h1>
        <form>
            <label for="username">Username:</label>
            <input type="text" id="username" v-model="username">
            <label for="password">Password:</label>
            <input type="password" id="password" v-model="password">
            <button type="button" @click="login">Login</button>
        </form>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import router from '../router';

const username = ref('');
const password = ref('');

function login() {
}
</script>

axios封装:

javascript 复制代码
// 1.引入axios
import axios from "axios";

// 2.创建axios对象
const service = axios.create({
    baseURL: 'http://localhost:3000'
});

// 3.设置请求拦截器 请求前进行一些操作 比如添加请求头、设置loading动画等
service.interceptors.request.use(config => {
    // 在请求头中添加token
    const token = localStorage.getItem('token');
    if (token) {
        config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
}, err => {
    Promise.reject(error)
})

// 4.设置响应拦截器 后端给前端返回数据 可以处理http状态码
service.interceptors.response.use(
    (response) => {
        if (response.status === 200) {
            return response.data
        }
    },
    error => {
        if (error.response) {
            switch (error.response.status) {
                case 401:
                    // 处理未授权
                    console.log('请检查账号密码')
                    break
                case 403:
                    // 处理禁止访问
                    console.log('禁止访问')
                    break
                case 404:
                    // 处理未找到
                    console.log('未找到')
                    break
                case 500:
                    // 处理服务器错误
                    console.log('服务器错误')
                    break
            }
        }
        throw error
    }
)

export default service

模块化:

javascript 复制代码
import request from '../request';

// 登录
export function getLogin(userName,password){
    return request({
        url: '/login',
        method: 'post',
        data: {
            username: userName,
            password: password
        }
    })
}

这样我们直接调用这个函数就能发送请求了。

在登录页面中导入对应的函数并使用:

javascript 复制代码
import { getLogin } from '../utils/api/users';

function login() {
    getLogin(username.value, password.value).then(res => {
        if(res.success){
            // 将token存入localStorage
            localStorage.setItem('token', res.token);
            // 跳转到首页
            router.push('/');
        }
    }).catch(err => {
        throw err;
    });
}

在路由中配置路由守卫来防止url跳转:

javascript 复制代码
// 配置路由守卫
router.beforeEach((to, from, next) => {
    const token = localStorage.getItem('token')
    const isAuthenticated = !!token
    
    // 如果路由需要认证但用户未登录
    if (to.meta.requiresAuth && !isAuthenticated) {
        return next('/login')
    }
    
    // 如果路由要求未登录(如登录页)但用户已登录
    if (to.meta.requiresGuest && isAuthenticated) {
        return next('/home') // 重定向到首页
    }
    
    // 其他情况正常放行
    next()
})

这样基本的登录功能就完成了

Token 验证中间件

在一些请求中需要携带token,否则无法请求到数据,就可以封装一个token验证中间件。

javascript 复制代码
function authenticateToken(req, res, next) {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1]; // 提取Bearer Token
    
    if (!token) {
        return res.status(401).json({
            success: false,
            message: '未提供认证Token'
        });
    }

    // 验证Token有效性
    jwt.verify(token, JWT_SECRET, (err, user) => {
        if (err) {
            return res.status(403).json({
                success: false,
                message: '无效的Token'
            });
        }
        req.user = user; // 将用户信息附加到请求对象
        next(); // 继续后续处理
    });
}

获取用户列表

javascript 复制代码
app.get('/users', authenticateToken, async (req, res) => {
    try {
        // 只返回必要字段,不包含密码
        const sql = 'SELECT id, username FROM user';
        const users = await query(sql)
        res.json({
            success: true,
            data: users
        })
    } catch (err) {
        console.error('Database error:', err);
        res.status(500).json({
            success: false,
            message: '获取用户列表失败'
        });
    }
})

获取用户列表时就需要在请求头中添加token,在axios的二次封装中,在请求拦截器中就可以在请求头中统一添加token。

javascript 复制代码
service.interceptors.request.use(config => {
    // 在请求头中添加token
    const token = localStorage.getItem('token');
    if (token) {
        config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
}, err => {
    Promise.reject(error)
})

模块化:

javascript 复制代码
export function getUsers() {
    return request({
        url: '/users',
    })
}

三、安全最佳实践

  1. 密码存储:实际项目中应使用bcrypt等库进行哈希处理
  2. 环境变量:敏感信息(如数据库密码、JWT密钥)应存储在环境变量中
  3. HTTPS:生产环境必须启用HTTPS
  4. SQL注入防护:始终使用参数化查询
  5. Token安全:设置合理的过期时间,考虑实现刷新Token机制
相关推荐
yhole15 小时前
如何升级node.js版本
node.js
Luna-player15 小时前
vue3,单页应用学习笔记
node.js
天远云服16 小时前
天远企业司法认证API对接实战:PHP构建B2B供应链合规防火墙
大数据·开发语言·后端·node.js·php
lzp079118 小时前
如何在Windows系统上安装和配置Node.js及Node版本管理器(nvm)
windows·node.js
weiwx8321 小时前
【前端】Node.js使用教程
前端·node.js·vim
i建模1 天前
Ubuntu Node.js 升级方案
linux·运维·ubuntu·node.js
结网的兔子1 天前
前端学习笔记(实战准备篇)——用vite构建一个项目【吐血整理】
前端·学习·elementui·npm·node.js·vue
i建模1 天前
npm国内镜像源加速
前端·npm·node.js
热爱生活的五柒2 天前
解决 npm install 一直在转圈的问题
前端·npm·node.js
跟着珅聪学java2 天前
Electron 中实现“字符串转图片”功能教程
node.js