从零到一构建Vue+Node.js+MySQL全栈CRUD应用
本文档详细介绍了如何从零开始构建一个基于Vue.js前端、Node.js+Express后端和MySQL数据库的完整CRUD(创建、读取、更新、删除)应用程序。
目录
项目概述
本项目是一个完整的Web应用,允许用户对数据库中的用户信息进行CRUD操作。项目采用前后端分离架构,前端使用Vue.js框架,后端使用Node.js和Express框架,数据存储在MySQL数据库中。
主要功能包括:
- 查看所有用户列表
- 添加新用户
- 编辑现有用户信息
- 删除用户
- 搜索用户
技术栈
前端
- Vue.js 2.x
- Vue Router
- Axios
- Bootstrap 4
后端
- Node.js
- Express
- CORS
- body-parser
- MySQL
项目结构
nyap/
├── client/ # 前端Vue.js项目
│ ├── public/
│ ├── src/
│ │ ├── components/ # Vue组件
│ │ │ ├── AddUser.vue # 添加用户组件
│ │ │ ├── User.vue # 用户详情/编辑组件
│ │ │ └── UsersList.vue # 用户列表组件
│ │ ├── services/ # API服务
│ │ │ └── UserService.js # 用户相关API调用
│ │ ├── router/ # 路由配置
│ │ │ └── index.js
│ │ ├── App.vue # 根组件
│ │ └── main.js # 入口文件
│ ├── package.json
│ └── vue.config.js # Vue配置
│
├── server/ # 后端Node.js项目
│ ├── index.js # 服务器入口
│ ├── database.sql # 数据库初始化脚本
│ └── package.json
│
└── README.md # 项目说明
开发环境配置
前置条件
- Node.js (v14+)
- npm 或 yarn
- MySQL 数据库
后端开发
创建项目
- 创建项目目录:
mkdir nyap
cd nyap
mkdir server
cd server
- 初始化Node.js项目:
npm init -y
安装依赖
npm install express cors body-parser mysql --save
npm install nodemon --save-dev
- 更新
package.json
中的scripts:
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
}
配置数据库
- 创建
database.sql
文件来初始化数据库:
-- 创建数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS nyap;
-- 使用数据库
USE nyap;
-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入一些示例数据
INSERT INTO users (name, email, phone) VALUES
('张三', 'zhangsan@example.com', '13800138001'),
('李四', 'lisi@example.com', '13800138002'),
('王五', 'wangwu@example.com', '13800138003');
- 执行SQL脚本:
mysql -u root -p < database.sql
创建服务器
创建index.js
文件,配置Express服务器:
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const mysql = require('mysql');
const app = express();
const PORT = process.env.PORT || 8080;
// 中间件
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// 创建MySQL连接
const connection = mysql.createConnection({
host: 'localhost',
user: 'root', // 替换为您的MySQL用户名
password: 'root', // 替换为您的MySQL密码
database: 'nyap' // 替换为您的数据库名
});
// 连接到MySQL
connection.connect(err => {
if (err) {
console.error('连接到MySQL失败:', err);
return;
}
console.log('成功连接到MySQL数据库');
});
// API路由定义...(见下一节)
// 启动服务器
app.listen(PORT, () => {
console.log(`服务器运行在端口 ${PORT} 上`);
});
实现API
在index.js
中添加API路由:
// 获取所有用户
app.get('/api/users', (req, res) => {
connection.query('SELECT * FROM users', (err, results) => {
if (err) {
console.error('获取用户失败:', err);
return res.status(500).json({ message: '获取用户失败', error: err });
}
res.json(results);
});
});
// 获取单个用户
app.get('/api/users/:id', (req, res) => {
connection.query('SELECT * FROM users WHERE id = ?', [req.params.id], (err, results) => {
if (err) {
console.error('获取用户失败:', err);
return res.status(500).json({ message: '获取用户失败', error: err });
}
if (results.length === 0) {
return res.status(404).json({ message: '未找到用户' });
}
res.json(results[0]);
});
});
// 创建用户
app.post('/api/users', (req, res) => {
const { name, email, phone } = req.body;
if (!name || !email) {
return res.status(400).json({ message: '名称和邮箱是必需的' });
}
connection.query(
'INSERT INTO users (name, email, phone) VALUES (?, ?, ?)',
[name, email, phone || null],
(err, results) => {
if (err) {
console.error('创建用户失败:', err);
return res.status(500).json({ message: '创建用户失败', error: err });
}
res.status(201).json({
id: results.insertId,
name,
email,
phone
});
}
);
});
// 更新用户
app.put('/api/users/:id', (req, res) => {
const { name, email, phone } = req.body;
if (!name || !email) {
return res.status(400).json({ message: '名称和邮箱是必需的' });
}
connection.query(
'UPDATE users SET name = ?, email = ?, phone = ? WHERE id = ?',
[name, email, phone || null, req.params.id],
(err, results) => {
if (err) {
console.error('更新用户失败:', err);
return res.status(500).json({ message: '更新用户失败', error: err });
}
if (results.affectedRows === 0) {
return res.status(404).json({ message: '未找到用户' });
}
res.json({ id: req.params.id, name, email, phone });
}
);
});
// 删除用户
app.delete('/api/users/:id', (req, res) => {
connection.query('DELETE FROM users WHERE id = ?', [req.params.id], (err, results) => {
if (err) {
console.error('删除用户失败:', err);
return res.status(500).json({ message: '删除用户失败', error: err });
}
if (results.affectedRows === 0) {
return res.status(404).json({ message: '未找到用户' });
}
res.json({ message: '用户已成功删除' });
});
});
启动后端服务
npm run dev
前端开发
创建项目
- 创建Vue项目:
cd ..
mkdir client
cd client
- 初始化Node.js项目:
npm init -y
安装依赖
npm install vue vue-router axios bootstrap
npm install @vue/cli-service @vue/cli-plugin-babel --save-dev
- 更新
package.json
中的scripts:
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test": "echo \"Error: no test specified\" && exit 1"
}
- 创建
vue.config.js
配置文件:
module.exports = {
devServer: {
port: 8081
}
}
配置路由
- 创建路由目录和文件:
mkdir -p src/router
- 创建
src/router/index.js
:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const routes = [
{
path: '/',
alias: '/users',
name: 'users',
component: () => import('../components/UsersList.vue')
},
{
path: '/users/:id',
name: 'user-details',
component: () => import('../components/User.vue')
},
{
path: '/add',
name: 'add',
component: () => import('../components/AddUser.vue')
}
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
});
export default router;
创建服务
- 创建服务目录和文件:
mkdir -p src/services
- 创建
src/services/UserService.js
:
import axios from 'axios';
const API_URL = 'http://localhost:8080/api';
class UserService {
getAll() {
return axios.get(`${API_URL}/users`);
}
get(id) {
return axios.get(`${API_URL}/users/${id}`);
}
create(data) {
return axios.post(`${API_URL}/users`, data);
}
update(id, data) {
return axios.put(`${API_URL}/users/${id}`, data);
}
delete(id) {
return axios.delete(`${API_URL}/users/${id}`);
}
}
export default new UserService();
创建组件
- 创建组件目录:
mkdir -p src/components
- 创建
src/App.vue
根组件:
<template>
<div id="app">
<nav class="navbar navbar-expand navbar-dark bg-dark">
<router-link to="/" class="navbar-brand">Vue MySQL CRUD</router-link>
<div class="navbar-nav mr-auto">
<li class="nav-item">
<router-link to="/users" class="nav-link">用户列表</router-link>
</li>
<li class="nav-item">
<router-link to="/add" class="nav-link">添加用户</router-link>
</li>
</div>
</nav>
<div class="container mt-3">
<router-view />
</div>
</div>
</template>
<script>
export default {
name: 'App'
};
</script>
<style>
.list {
text-align: left;
max-width: 750px;
margin: auto;
}
</style>
- 创建
src/main.js
入口文件:
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import 'bootstrap/dist/css/bootstrap.min.css';
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App)
}).$mount('#app');
- 创建
src/components/UsersList.vue
组件:
<template>
<div class="list row">
<div class="col-md-8">
<div class="input-group mb-3">
<input
type="text"
class="form-control"
placeholder="搜索用户名"
v-model="searchName"
@keyup.enter="searchByName"
/>
<div class="input-group-append">
<button
class="btn btn-outline-secondary"
type="button"
@click="searchByName"
>
搜索
</button>
</div>
</div>
</div>
<div class="col-md-6">
<h4>用户列表</h4>
<ul class="list-group">
<li
class="list-group-item"
:class="{ active: index === currentIndex }"
v-for="(user, index) in users"
:key="index"
@click="setActiveUser(user, index)"
>
{{ user.name }}
</li>
</ul>
</div>
<div class="col-md-6">
<div v-if="currentUser">
<h4>用户信息</h4>
<div>
<label><strong>姓名:</strong></label> {{ currentUser.name }}
</div>
<div>
<label><strong>邮箱:</strong></label> {{ currentUser.email }}
</div>
<div>
<label><strong>电话:</strong></label> {{ currentUser.phone }}
</div>
<router-link
:to="'/users/' + currentUser.id"
class="badge badge-warning"
>
编辑
</router-link>
</div>
<div v-else>
<br />
<p>请点击用户...</p>
</div>
</div>
</div>
</template>
<script>
import UserService from "../services/UserService";
export default {
name: "users-list",
data() {
return {
users: [],
currentUser: null,
currentIndex: -1,
searchName: ""
};
},
methods: {
retrieveUsers() {
UserService.getAll()
.then(response => {
this.users = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
},
refreshList() {
this.retrieveUsers();
this.currentUser = null;
this.currentIndex = -1;
},
setActiveUser(user, index) {
this.currentUser = user;
this.currentIndex = index;
},
searchByName() {
// 在实际应用中,这里可能需要调用后端API进行搜索
// 在本例中,我们在前端进行筛选
UserService.getAll()
.then(response => {
this.users = response.data.filter(user =>
user.name.toLowerCase().includes(this.searchName.toLowerCase())
);
})
.catch(e => {
console.log(e);
});
}
},
mounted() {
this.retrieveUsers();
}
};
</script>
<style>
.list {
text-align: left;
max-width: 750px;
margin: auto;
}
</style>
- 创建
src/components/AddUser.vue
组件:
<template>
<div class="submit-form">
<div v-if="!submitted">
<div class="form-group">
<label for="name">姓名</label>
<input
type="text"
class="form-control"
id="name"
required
v-model="user.name"
name="name"
/>
</div>
<div class="form-group">
<label for="email">邮箱</label>
<input
type="email"
class="form-control"
id="email"
required
v-model="user.email"
name="email"
/>
</div>
<div class="form-group">
<label for="phone">电话</label>
<input
type="text"
class="form-control"
id="phone"
v-model="user.phone"
name="phone"
/>
</div>
<button @click="saveUser" class="btn btn-success">提交</button>
</div>
<div v-else>
<h4>用户添加成功!</h4>
<button class="btn btn-success" @click="newUser">添加另一个</button>
</div>
</div>
</template>
<script>
import UserService from "../services/UserService";
export default {
name: "add-user",
data() {
return {
user: {
id: null,
name: "",
email: "",
phone: ""
},
submitted: false
};
},
methods: {
saveUser() {
const data = {
name: this.user.name,
email: this.user.email,
phone: this.user.phone
};
UserService.create(data)
.then(response => {
this.user.id = response.data.id;
console.log(response.data);
this.submitted = true;
})
.catch(e => {
console.log(e);
});
},
newUser() {
this.submitted = false;
this.user = {
id: null,
name: "",
email: "",
phone: ""
};
}
}
};
</script>
<style>
.submit-form {
max-width: 300px;
margin: auto;
}
</style>
- 创建
src/components/User.vue
组件:
<template>
<div v-if="currentUser" class="edit-form">
<h4>用户信息</h4>
<form>
<div class="form-group">
<label for="name">姓名</label>
<input
type="text"
class="form-control"
id="name"
v-model="currentUser.name"
/>
</div>
<div class="form-group">
<label for="email">邮箱</label>
<input
type="email"
class="form-control"
id="email"
v-model="currentUser.email"
/>
</div>
<div class="form-group">
<label for="phone">电话</label>
<input
type="text"
class="form-control"
id="phone"
v-model="currentUser.phone"
/>
</div>
</form>
<button
type="submit"
class="badge badge-success"
@click="updateUser"
>
更新
</button>
<button class="badge badge-danger mr-2" @click="deleteUser">
删除
</button>
<p>{{ message }}</p>
</div>
<div v-else>
<br />
<p>请点击一个用户...</p>
</div>
</template>
<script>
import UserService from "../services/UserService";
export default {
name: "user",
data() {
return {
currentUser: null,
message: ""
};
},
methods: {
getUser(id) {
UserService.get(id)
.then(response => {
this.currentUser = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
},
updateUser() {
UserService.update(this.currentUser.id, this.currentUser)
.then(response => {
console.log(response.data);
this.message = "用户更新成功!";
})
.catch(e => {
console.log(e);
});
},
deleteUser() {
UserService.delete(this.currentUser.id)
.then(response => {
console.log(response.data);
this.$router.push({ name: "users" });
})
.catch(e => {
console.log(e);
});
}
},
mounted() {
this.message = "";
this.getUser(this.$route.params.id);
}
};
</script>
<style>
.edit-form {
max-width: 300px;
margin: auto;
}
</style>
启动前端服务
npm run serve
整合和测试
- 确保后端服务器运行在端口8080:
cd ../server
npm run dev
- 确保前端服务器运行在端口8081:
cd ../client
npm run serve
- 打开浏览器,访问 http://localhost:8081
API参考
方法 | URL | 说明 | 请求体 | 响应 |
---|---|---|---|---|
GET | /api/users | 获取所有用户 | 无 | 用户对象数组 |
GET | /api/users/:id | 获取单个用户 | 无 | 单个用户对象 |
POST | /api/users | 创建新用户 | {name, email, phone} | 创建的用户对象,带ID |
PUT | /api/users/:id | 更新用户 | {name, email, phone} | 更新后的用户对象 |
DELETE | /api/users/:id | 删除用户 | 无 | {message: '用户已成功删除'} |
常见问题和解决方案
1. 连接被拒绝错误 (ECONNREFUSED)
问题: 前端报错:POST http://localhost:8080/api/users net::ERR_CONNECTION_REFUSED
解决方案:
- 确保后端服务器正在运行 (
npm run dev
) - 确认后端服务器监听的端口是否正确 (8080)
- 前端服务中配置的API地址是否正确
2. 404错误
问题: 前端报错:POST http://localhost:8081/api/users 404 (Not Found)
解决方案:
- 检查API URL是否正确。前端应该连接到后端服务器 (8080),而不是前端服务器 (8081)
- 在
UserService.js
中修改API_URL:
const API_URL = 'http://localhost:8080/api';
3. 数据库连接错误
问题: 后端服务器报错:连接到MySQL失败
解决方案:
- 确保MySQL服务正在运行
- 检查数据库连接配置是否正确 (用户名、密码、数据库名)
- 确保数据库
nyap
已创建
优化和改进
-
添加认证机制:
- 实现JWT认证
- 添加登录/注册功能
-
优化前端:
- 添加表单验证
- 实现分页功能
- 添加更高级的搜索和筛选
-
增强后端:
- 使用环境变量管理配置
- 实现更好的错误处理
- 添加日志记录
- 设计更RESTful的API
-
测试:
- 添加单元测试
- 添加集成测试
-
部署:
- 添加部署脚本
- 配置持续集成/持续部署(CI/CD)
- 容器化(Docker)