在前 8 课中,我们已经完成了 Vue 3 前端任务管理系统的开发,但前端所有数据都依赖模拟接口,无法实现真实的数据持久化(比如刷新页面后任务数据丢失)。企业级项目中,后端负责提供数据存储、接口服务、身份验证等核心支撑。本节课将采用Node.js + Express + MongoDB技术栈(新手友好、前后端同语言),为前端任务管理系统搭建专属后端 API 服务,实现 "前端请求→后端处理→数据库存储" 的全栈联动,让你的项目具备真实生产环境的使用能力。
一、课前准备:后端开发环境搭建(10 分钟搞定)
1. 必备工具与环境
- Node.js & npm :后端运行环境,下载地址:https://nodejs.org/zh-cn/(安装 LTS 长期支持版,自带 npm);
- MongoDB :非关系型数据库(新手友好,无需建表语句),下载地址:https://www.mongodb.com/try/download/community(或使用云数据库 MongoDB Atlas,无需本地安装);
- MongoDB Compass :MongoDB 可视化管理工具,用于查看数据库数据,下载地址:https://www.mongodb.com/try/download/compass;
- Postman :接口测试工具,用于验证后端接口是否可用,下载地址:https://www.postman.com/downloads/;
- VS Code :后端代码编写工具(安装
JavaScript and TypeScript Nightly插件)。
2. 环境验证
打开终端(CMD / 终端),输入以下命令验证环境是否安装成功:
bash
运行
# 验证Node.js版本(需>=16.0.0)
node -v
# 验证npm版本
npm -v
3. 项目初始化
(1)创建后端项目文件夹
bash
运行
# 创建项目文件夹
mkdir vue-task-system-backend
# 进入文件夹
cd vue-task-system-backend
# 初始化npm项目(一路回车即可,生成package.json)
npm init -y
(2)安装核心依赖
bash
运行
# Express:轻量级后端框架,用于搭建API服务
npm install express -S
# mongoose:MongoDB数据库操作工具,简化数据增删改查
npm install mongoose -S
# cors:解决跨域问题(前端和后端端口不同导致的请求限制)
npm install cors -S
# jsonwebtoken:生成JWT令牌,用于用户身份验证
npm install jsonwebtoken -S
# bcryptjs:密码加密(比CryptoJS更安全,专门用于密码处理)
npm install bcryptjs -S
# dotenv:管理环境变量(如数据库地址、JWT密钥)
npm install dotenv -S
# nodemon:开发环境热重载(修改代码无需手动重启服务)
npm install nodemon -D
(3)配置 package.json 脚本
修改package.json,添加启动脚本:
json
{
"name": "vue-task-system-backend",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"start": "node app.js", // 生产环境启动
"dev": "nodemon app.js" // 开发环境启动(热重载)
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^7.6.3"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
(4)创建环境变量文件
新建.env文件,存储敏感配置(不要提交到代码仓库):
env
# 服务端口
PORT=3000
# MongoDB连接地址(本地数据库:mongodb://127.0.0.1:27017/vue_task_system)
# 云数据库(MongoDB Atlas)需替换为自己的连接地址
MONGODB_URL=mongodb://127.0.0.1:27017/vue_task_system
# JWT密钥(自定义,越长越安全)
JWT_SECRET=vue_task_system_backend_2024_jwt_secret
# JWT过期时间(24小时)
JWT_EXPIRES_IN=86400s
二、核心实操一:搭建后端基础架构与数据库连接
1. 项目目录结构
先搭建清晰的目录结构,便于后续维护(新手严格按照此结构创建):
plaintext
vue-task-system-backend/
├── .env # 环境变量
├── package.json # 项目配置
├── app.js # 入口文件
├── config/ # 配置文件夹
│ └── db.js # 数据库连接配置
├── models/ # 数据模型文件夹(对应MongoDB集合)
│ ├── User.js # 用户模型
│ ├── Todo.js # 任务模型
│ └── Category.js # 分类模型
├── routes/ # 路由文件夹(对应接口模块)
│ ├── userRoutes.js # 用户相关路由
│ ├── todoRoutes.js # 任务相关路由
│ └── categoryRoutes.js # 分类相关路由
├── middleware/ # 中间件文件夹
│ ├── auth.js # JWT身份验证中间件
│ └── response.js # 统一响应格式中间件
└── utils/ # 工具函数文件夹
└── index.js # 通用工具(如密码加密、JWT生成)
2. 步骤 1:数据库连接配置(config/db.js)
javascript
运行
const mongoose = require('mongoose');
require('dotenv').config();
// 连接MongoDB数据库
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URL, {
// MongoDB 6.0+无需配置这些参数,可省略
// useNewUrlParser: true,
// useUnifiedTopology: true
});
console.log(`MongoDB连接成功:${conn.connection.host}`);
} catch (error) {
console.error(`MongoDB连接失败:${error.message}`);
process.exit(1); // 连接失败退出进程
}
};
module.exports = connectDB;
3. 步骤 2:统一响应格式中间件(middleware/response.js)
前端需要统一的响应格式,方便处理数据,避免每次接口返回不同格式:
javascript
运行
// 统一成功响应
const successResponse = (res, data = null, message = '操作成功') => {
res.status(200).json({
code: 200,
message,
data
});
};
// 统一错误响应
const errorResponse = (res, message = '操作失败', code = 500) => {
res.status(code).json({
code,
message,
data: null
});
};
module.exports = {
successResponse,
errorResponse
};
4. 步骤 3:JWT 身份验证中间件(middleware/auth.js)
验证前端请求头中的 JWT 令牌,确认用户是否登录,用于保护需要权限的接口:
javascript
运行
const jwt = require('jsonwebtoken');
const { errorResponse } = require('./response');
require('dotenv').config();
// 身份验证中间件
const protect = (req, res, next) => {
let token;
// 从请求头中获取token(格式:Bearer <token>)
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
try {
// 提取token(去掉Bearer前缀)
token = req.headers.authorization.split(' ')[1];
// 验证token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 将用户ID存入请求对象,后续接口可直接使用
req.user = { id: decoded.id };
next(); // 验证通过,执行下一个中间件/接口
} catch (error) {
console.error(error);
errorResponse(res, '无效的令牌,请重新登录', 401);
}
}
if (!token) {
errorResponse(res, '未提供令牌,无权访问', 401);
}
};
module.exports = { protect };
5. 步骤 4:工具函数(utils/index.js)
封装密码加密和 JWT 生成工具,避免重复代码:
javascript
运行
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
require('dotenv').config();
// 密码加密
const encryptPassword = async (password) => {
// 生成盐值(rounds=10,越高越安全,速度越慢)
const salt = await bcrypt.genSalt(10);
// 加密密码
return await bcrypt.hash(password, salt);
};
// 密码验证(对比明文密码和加密密码是否一致)
const verifyPassword = async (plainPassword, hashedPassword) => {
return await bcrypt.compare(plainPassword, hashedPassword);
};
// 生成JWT令牌
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN
});
};
module.exports = {
encryptPassword,
verifyPassword,
generateToken
};
6. 步骤 5:入口文件(app.js)
整合所有配置,启动后端服务:
javascript
运行
const express = require('express');
const cors = require('cors');
const connectDB = require('./config/db');
const { successResponse, errorResponse } = require('./middleware/response');
require('dotenv').config();
// 连接数据库
connectDB();
// 创建Express应用
const app = express();
const PORT = process.env.PORT || 3000;
// 中间件配置
app.use(cors()); // 允许所有跨域请求(生产环境可配置指定域名)
app.use(express.json()); // 解析JSON格式请求体
app.use(express.urlencoded({ extended: false })); // 解析表单格式请求体
// 测试接口
app.get('/', (req, res) => {
successResponse(res, { message: 'Vue任务管理系统后端服务运行中' }, '服务正常');
});
// 挂载路由(所有接口前缀为/api)
app.use('/api/users', require('./routes/userRoutes'));
app.use('/api/categories', require('./routes/categoryRoutes'));
app.use('/api/todos', require('./routes/todoRoutes'));
// 404接口
app.use('*', (req, res) => {
errorResponse(res, '接口不存在', 404);
});
// 启动服务
app.listen(PORT, () => {
console.log(`后端服务启动成功,运行在端口:${PORT}`);
});
三、核心实操二:定义数据模型(MongoDB 集合)
数据模型对应 MongoDB 的集合(类似 MySQL 的表),定义数据结构和验证规则。
1. 用户模型(models/User.js)
javascript
运行
const mongoose = require('mongoose');
// 用户模型 Schema
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, '用户名不能为空'],
unique: true, // 用户名唯一
trim: true, // 去除前后空格
minlength: [3, '用户名长度不少于3位'],
maxlength: [20, '用户名长度不超过20位']
},
password: {
type: String,
required: [true, '密码不能为空'],
minlength: [6, '密码长度不少于6位']
},
email: {
type: String,
required: [true, '邮箱不能为空'],
unique: true,
trim: true,
match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, '请输入有效的邮箱']
},
avatar: {
type: String,
default: 'https://picsum.photos/200/200' // 默认头像
},
createTime: {
type: Date,
default: Date.now
}
}, {
timestamps: true // 自动添加createdAt和updatedAt字段
});
// 定义模型
const User = mongoose.model('User', userSchema);
module.exports = User;
2. 分类模型(models/Category.js)
javascript
运行
const mongoose = require('mongoose');
// 分类模型 Schema
const categorySchema = new mongoose.Schema({
name: {
type: String,
required: [true, '分类名称不能为空'],
unique: true,
trim: true
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User', // 关联用户模型
required: [true, '用户ID不能为空'] // 每个分类归属一个用户
},
createTime: {
type: Date,
default: Date.now
}
}, {
timestamps: true
});
// 定义模型
const Category = mongoose.model('Category', categorySchema);
module.exports = Category;
3. 任务模型(models/Todo.js)
javascript
运行
const mongoose = require('mongoose');
// 任务模型 Schema
const todoSchema = new mongoose.Schema({
title: {
type: String,
required: [true, '任务内容不能为空'],
trim: true
},
completed: {
type: Boolean,
default: false // 默认未完成
},
categoryId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category', // 关联分类模型
required: [true, '分类ID不能为空']
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User', // 关联用户模型
required: [true, '用户ID不能为空']
},
createTime: {
type: Date,
default: Date.now
}
}, {
timestamps: true
});
// 定义模型
const Todo = mongoose.model('Todo', todoSchema);
module.exports = Todo;
四、核心实操三:实现业务接口(路由)
1. 用户相关接口(routes/userRoutes.js)
实现注册、登录、获取用户信息功能:
javascript
运行
const express = require('express');
const router = express.Router();
const User = require('../models/User');
const { successResponse, errorResponse } = require('../middleware/response');
const { protect } = require('../middleware/auth');
const { encryptPassword, verifyPassword, generateToken } = require('../utils');
// 1. 用户注册
router.post('/register', async (req, res) => {
try {
const { username, password, email } = req.body;
// 检查用户名是否已存在
const userExists = await User.findOne({ username });
if (userExists) {
return errorResponse(res, '用户名已存在', 400);
}
// 检查邮箱是否已存在
const emailExists = await User.findOne({ email });
if (emailExists) {
return errorResponse(res, '邮箱已存在', 400);
}
// 密码加密
const hashedPassword = await encryptPassword(password);
// 创建用户
const user = await User.create({
username,
password: hashedPassword,
email
});
if (user) {
successResponse(res, {
id: user._id,
username: user.username,
email: user.email,
avatar: user.avatar,
token: generateToken(user._id) // 注册成功直接返回token
}, '注册成功');
} else {
errorResponse(res, '创建用户失败', 400);
}
} catch (error) {
console.error(error);
errorResponse(res, '服务器错误');
}
});
// 2. 用户登录
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
// 查找用户
const user = await User.findOne({ username });
if (!user) {
return errorResponse(res, '用户名不存在', 400);
}
// 验证密码
const isPasswordValid = await verifyPassword(password, user.password);
if (!isPasswordValid) {
return errorResponse(res, '密码错误', 400);
}
// 返回用户信息和token
successResponse(res, {
id: user._id,
username: user.username,
email: user.email,
avatar: user.avatar,
token: generateToken(user._id)
}, '登录成功');
} catch (error) {
console.error(error);
errorResponse(res, '服务器错误');
}
});
// 3. 获取当前用户信息(需要身份验证)
router.get('/me', protect, async (req, res) => {
try {
// 根据用户ID查找用户(排除密码字段)
const user = await User.findById(req.user.id).select('-password');
if (!user) {
return errorResponse(res, '用户不存在', 400);
}
successResponse(res, user, '获取用户信息成功');
} catch (error) {
console.error(error);
errorResponse(res, '服务器错误');
}
});
module.exports = router;
2. 分类相关接口(routes/categoryRoutes.js)
实现分类增删改查功能:
javascript
运行
const express = require('express');
const router = express.Router();
const Category = require('../models/Category');
const { successResponse, errorResponse } = require('../middleware/response');
const { protect } = require('../middleware/auth');
// 1. 获取当前用户所有分类(需要身份验证)
router.get('/', protect, async (req, res) => {
try {
const categories = await Category.find({ userId: req.user.id }).sort({ createTime: -1 });
successResponse(res, categories, '获取分类列表成功');
} catch (error) {
console.error(error);
errorResponse(res, '服务器错误');
}
});
// 2. 新增分类(需要身份验证)
router.post('/', protect, async (req, res) => {
try {
const { name } = req.body;
// 检查分类名称是否已存在
const categoryExists = await Category.findOne({ name, userId: req.user.id });
if (categoryExists) {
return errorResponse(res, '分类名称已存在', 400);
}
// 创建分类
const category = await Category.create({
name,
userId: req.user.id
});
successResponse(res, category, '新增分类成功');
} catch (error) {
console.error(error);
errorResponse(res, '服务器错误');
}
});
// 3. 删除分类(需要身份验证)
router.delete('/:id', protect, async (req, res) => {
try {
const categoryId = req.params.id;
// 检查分类是否存在且归属当前用户
const category = await Category.findOne({ _id: categoryId, userId: req.user.id });
if (!category) {
return errorResponse(res, '分类不存在或无权删除', 400);
}
// 删除分类
await Category.findByIdAndDelete(categoryId);
successResponse(res, null, '删除分类成功');
} catch (error) {
console.error(error);
errorResponse(res, '服务器错误');
}
});
module.exports = router;
3. 任务相关接口(routes/todoRoutes.js)
实现任务增删改查、状态更新功能:
javascript
运行
const express = require('express');
const router = express.Router();
const Todo = require('../models/Todo');
const { successResponse, errorResponse } = require('../middleware/response');
const { protect } = require('../middleware/auth');
// 1. 获取当前用户所有任务(需要身份验证)
router.get('/', protect, async (req, res) => {
try {
const todos = await Todo.find({ userId: req.user.id }).sort({ createTime: -1 });
successResponse(res, todos, '获取任务列表成功');
} catch (error) {
console.error(error);
errorResponse(res, '服务器错误');
}
});
// 2. 新增任务(需要身份验证)
router.post('/', protect, async (req, res) => {
try {
const { title, categoryId } = req.body;
// 创建任务
const todo = await Todo.create({
title,
categoryId,
userId: req.user.id
});
successResponse(res, todo, '新增任务成功');
} catch (error) {
console.error(error);
errorResponse(res, '服务器错误');
}
});
// 3. 更新任务状态(需要身份验证)
router.put('/:id/status', protect, async (req, res) => {
try {
const todoId = req.params.id;
const { completed } = req.body;
// 检查任务是否存在且归属当前用户
const todo = await Todo.findOne({ _id: todoId, userId: req.user.id });
if (!todo) {
return errorResponse(res, '任务不存在或无权修改', 400);
}
// 更新任务状态
const updatedTodo = await Todo.findByIdAndUpdate(
todoId,
{ completed },
{ new: true } // 返回更新后的任务
);
successResponse(res, updatedTodo, '更新任务状态成功');
} catch (error) {
console.error(error);
errorResponse(res, '服务器错误');
}
});
// 4. 删除任务(需要身份验证)
router.delete('/:id', protect, async (req, res) => {
try {
const todoId = req.params.id;
// 检查任务是否存在且归属当前用户
const todo = await Todo.findOne({ _id: todoId, userId: req.user.id });
if (!todo) {
return errorResponse(res, '任务不存在或无权删除', 400);
}
// 删除任务
await Todo.findByIdAndDelete(todoId);
successResponse(res, null, '删除任务成功');
} catch (error) {
console.error(error);
errorResponse(res, '服务器错误');
}
});
module.exports = router;
五、核心实操四:接口测试与前后端联调
1. 启动后端服务
bash
运行
# 启动开发环境(热重载)
npm run dev
启动成功后,终端会显示:
plaintext
MongoDB连接成功:127.0.0.1
后端服务启动成功,运行在端口:3000
2. 用 Postman 测试接口
(1)测试注册接口
- 请求地址:
http://localhost:3000/api/users/register - 请求方式:POST
- 请求体(JSON):
json
{
"username": "testuser",
"password": "123456",
"email": "testuser@163.com"
}
- 响应结果:返回用户信息和 token,说明注册成功。
(2)测试登录接口
- 请求地址:
http://localhost:3000/api/users/login - 请求方式:POST
- 请求体(JSON):
json
{
"username": "testuser",
"password": "123456"
}
- 响应结果:返回用户信息和 token,说明登录成功。
(3)测试新增分类接口(需要身份验证)
- 请求地址:
http://localhost:3000/api/categories - 请求方式:POST
- 请求头:
Authorization: Bearer <登录返回的token> - 请求体(JSON):
json
{
"name": "工作"
}
- 响应结果:返回分类信息,说明新增分类成功。
3. 前端对接后端接口
修改前端项目的src/utils/axios.js,更新基础地址并添加请求头携带 token:
javascript
运行
import axios from 'axios';
import { ElMessage } from 'element-plus';
import { useUserStore } from '@/stores/user';
// 创建axios实例
const service = axios.create({
// 替换为后端服务地址
baseURL: 'http://localhost:3000/api',
timeout: 5000
});
// 请求拦截器:添加JWT token
service.interceptors.request.use(
(config) => {
const userStore = useUserStore();
if (userStore.token) {
// 给请求头添加Authorization字段
config.headers.Authorization = `Bearer ${userStore.token}`;
}
return config;
},
(error) => {
console.error('请求错误:', error);
return Promise.reject(error);
}
);
// 响应拦截器:统一处理响应
service.interceptors.response.use(
(response) => {
const res = response.data;
// 成功响应
if (res.code === 200) {
return res;
} else {
// 业务错误
ElMessage.error(res.message || '请求失败');
return Promise.reject(res);
}
},
(error) => {
// 网络错误或服务器错误
ElMessage.error(error.message || '服务器错误');
return Promise.reject(error);
}
);
export default service;
修改前端各 API 文件(以src/api/user.js为例),对接真实后端接口:
javascript
运行
import request from '../utils/axios';
// 登录接口
export const userLogin = (userData) => {
return request({
url: '/users/login',
method: 'POST',
data: userData
});
};
// 获取当前用户信息
export const getUserInfo = () => {
return request({
url: '/users/me',
method: 'GET'
});
};
// 注册接口(新增)
export const userRegister = (userData) => {
return request({
url: '/users/register',
method: 'POST',
data: userData
});
};
4. 前后端联调测试
启动前端项目(npm run dev),访问http://127.0.0.1:5173/,进行以下操作:
- 注册新用户(若未注册);
- 登录用户,进入系统;
- 新增分类、新增任务、修改任务状态、删除任务;
- 刷新页面,数据不会丢失(已持久化到 MongoDB);
- 退出登录后重新登录,数据依然存在。
六、综合实战:后端部署(可选,生产环境)
1. 部署到云服务器(以阿里云 ECS 为例)
-
云服务器安装 Node.js、MongoDB;
-
上传后端项目到服务器;
-
安装依赖:
npm install --production(生产环境不安装开发依赖); -
用
pm2启动服务(进程守护,防止服务意外停止):bash
运行
# 安装pm2 npm install pm2 -g # 启动服务 pm2 start app.js --name vue-task-system-backend # 设置开机自启 pm2 startup # 保存进程配置 pm2 save
2. 部署到云函数(无需服务器,新手友好)
- 腾讯云 SCF / 阿里云 FC:将 Express 项目改造为云函数格式,直接部署;
- Vercel:支持 Node.js 后端部署,无需复杂配置,关联 GitHub 仓库即可自动部署。
七、本节课总结与后续学习建议
1. 本节课核心收获
- 环境搭建:掌握 Node.js + Express + MongoDB 后端开发环境搭建;
- 核心技能:掌握数据库连接、数据模型定义、接口开发、身份验证(JWT);
- 全栈联动:实现前端与后端的真实数据交互,理解全栈项目的工作流程;
- 项目落地:让前端任务管理系统具备真实数据持久化能力,可用于生产环境。
2. 课后作业(必做)
- 独立完成后端项目搭建,确保所有接口正常工作;
- 完成前端与后端的对接,实现注册、登录、任务 / 分类管理的全流程;
- 新增 "修改用户信息""编辑任务" 接口,完善业务功能;
- 部署后端项目到云服务器或云函数,生成线上接口地址。
3. 后续后端学习建议
- 技术深化:学习 Koa2(更优雅的 Node.js 框架)、TypeScript + Node.js;
- 数据库拓展:学习 MySQL(关系型数据库)、Sequelize(ORM 工具);
- 安全优化:学习接口限流、SQL 注入防护、XSS 攻击防护;
- 框架学习:学习 Spring Boot(Java 后端)、Django(Python 后端),拓展技术栈;
- 微服务:学习微服务架构、Docker 容器化部署,应对大型项目开发。
至此,你已掌握 "Vue 3 前端 + Node.js 后端" 的全栈开发能力,能够独立开发中小型全栈项目。后续可通过持续实战,不断提升后端架构设计和性能优化能力,成为一名全栈开发工程师!
明天开始进行新的课程讲解!