🌈个人主页:前端青山
🔥系列专栏:node.js篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来node.js篇专栏内容:Node.js + MongoDB + Vue 3 全栈应用项目开发
在前几篇文章中,我们已经为 Node.js 应用添加了身份验证、CORS 配置、缓存机制、性能监控、限流功能和日志优化。本文将继续在这个基础上,逐步构建一个完整的 Node.js + MongoDB + Vue 3 全栈应用。我们将从项目结构设计、前后端交互、数据模型设计等方面入手,逐步实现一个功能完善的全栈应用。
目录
[1. 项目概述](#1. 项目概述)
[2. 项目结构设计](#2. 项目结构设计)
[3. 数据模型设计](#3. 数据模型设计)
[3.1 用户模型](#3.1 用户模型)
[3.2 任务模型](#3.2 任务模型)
[4. 后端 API 开发](#4. 后端 API 开发)
[4.1 用户模块](#4.1 用户模块)
[4.1.1 用户注册](#4.1.1 用户注册)
[4.1.2 用户登录](#4.1.2 用户登录)
[4.1.3 获取用户信息](#4.1.3 获取用户信息)
[4.2 项目模块](#4.2 项目模块)
[4.2.1 创建任务](#4.2.1 创建任务)
[4.2.2 获取任务列表](#4.2.2 获取任务列表)
[4.2.3 更新任务](#4.2.3 更新任务)
[4.2.4 删除任务](#4.2.4 删除任务)
[5. 前端 Vue 3 应用开发](#5. 前端 Vue 3 应用开发)
[5.1 项目初始化](#5.1 项目初始化)
[5.2 组件开发](#5.2 组件开发)
[5.2.1 登录组件](#5.2.1 登录组件)
[5.2.2 注册组件](#5.2.2 注册组件)
[5.2.3 任务列表组件](#5.2.3 任务列表组件)
[5.3 状态管理](#5.3 状态管理)
[6. 前后端联调](#6. 前后端联调)
[6.1 路由配置](#6.1 路由配置)
[6.2 主页组件](#6.2 主页组件)
[6.3 登录和注册页面](#6.3 登录和注册页面)
[7. 部署与测试](#7. 部署与测试)
[7.1 部署后端](#7.1 部署后端)
[7.2 部署前端](#7.2 部署前端)
[7.3 测试](#7.3 测试)
[8. 总结与展望](#8. 总结与展望)
1. 项目概述
我们的目标是构建一个简单的任务管理应用,用户可以注册、登录、创建和管理任务。应用的主要功能包括:
- 用户注册和登录
- 用户信息管理
- 任务创建、编辑和删除
- 任务列表展示
2. 项目结构设计
为了保持项目的清晰和可维护性,我们将项目分为前后端两部分。项目结构如下:
javascript
task-manager/
├── backend/
│ ├── node_modules/
│ ├── src/
│ │ ├── controllers/
│ │ ├── models/
│ │ ├── routes/
│ │ ├── middlewares/
│ │ ├── config/
│ │ ├── app.js
│ │ ├── server.js
│ ├── .env
│ ├── package.json
│ └── README.md
├── frontend/
│ ├── node_modules/
│ ├── public/
│ │ └── index.html
│ ├── src/
│ │ ├── assets/
│ │ ├── components/
│ │ ├── views/
│ │ ├── store/
│ │ ├── router/
│ │ ├── App.vue
│ │ └── main.js
│ ├── .env
│ ├── package.json
│ └── README.md
├── .gitignore
└── README.md
3. 数据模型设计
我们将使用 MongoDB 作为数据库,定义两个主要的数据模型:用户和任务。
3.1 用户模型
用户模型包含以下字段:
username
:用户名email
:邮箱password
:密码(加密存储)createdAt
:创建时间updatedAt
:更新时间
在 backend/src/models/user.js
中定义用户模型:
javascript
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const UserSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
UserSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
UserSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
const User = mongoose.model('User', UserSchema);
module.exports = User;
3.2 任务模型
任务模型包含以下字段:
title
:任务标题description
:任务描述status
:任务状态(未完成、已完成)userId
:关联用户 IDcreatedAt
:创建时间updatedAt
:更新时间
在 backend/src/models/task.js
中定义任务模型:
javascript
const mongoose = require('mongoose');
const TaskSchema = new mongoose.Schema({
title: { type: String, required: true },
description: { type: String, required: true },
status: { type: String, enum: ['未完成', '已完成'], default: '未完成' },
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
const Task = mongoose.model('Task', TaskSchema);
module.exports = Task;
4. 后端 API 开发
4.1 用户模块
4.1.1 用户注册
在 backend/src/controllers/userController.js
中实现用户注册功能:
javascript
const User = require('../models/user');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
exports.register = async (req, res) => {
const { username, email, password } = req.body;
try {
let user = await User.findOne({ email });
if (user) {
return res.status(400).json({ msg: 'User already exists' });
}
user = new User({ username, email, password });
await user.save();
const payload = { user: { id: user.id } };
const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' });
res.status(201).json({ token });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
4.1.2 用户登录
在 backend/src/controllers/userController.js
中实现用户登录功能:
javascript
exports.login = async (req, res) => {
const { email, password } = req.body;
try {
let user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ msg: 'Invalid credentials' });
}
const isMatch = await user.comparePassword(password);
if (!isMatch) {
return res.status(400).json({ msg: 'Invalid credentials' });
}
const payload = { user: { id: user.id } };
const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
4.1.3 获取用户信息
在 backend/src/controllers/userController.js
中实现获取用户信息功能:
javascript
exports.getUser = async (req, res) => {
try {
const user = await User.findById(req.user.id).select('-password');
if (!user) {
return res.status(404).json({ msg: 'User not found' });
}
res.json(user);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
4.2 项目模块
4.2.1 创建任务
在 backend/src/controllers/taskController.js
中实现创建任务功能:
javascript
const Task = require('../models/task');
exports.createTask = async (req, res) => {
const { title, description } = req.body;
try {
const task = new Task({ title, description, userId: req.user.id });
await task.save();
res.status(201).json(task);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
4.2.2 获取任务列表
在 backend/src/controllers/taskController.js
中实现获取任务列表功能:
javascript
exports.getTasks = async (req, res) => {
try {
const tasks = await Task.find({ userId: req.user.id }).sort({ createdAt: -1 });
res.json(tasks);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
4.2.3 更新任务
在 backend/src/controllers/taskController.js
中实现更新任务功能:
javascript
exports.updateTask = async (req, res) => {
const { title, description, status } = req.body;
try {
let task = await Task.findById(req.params.id);
if (!task) {
return res.status(404).json({ msg: 'Task not found' });
}
if (task.userId.toString() !== req.user.id) {
return res.status(401).json({ msg: 'Not authorized' });
}
task.title = title;
task.description = description;
task.status = status;
await task.save();
res.json(task);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
4.2.4 删除任务
在 backend/src/controllers/taskController.js
中实现删除任务功能:
javascript
exports.deleteTask = async (req, res) => {
try {
let task = await Task.findById(req.params.id);
if (!task) {
return res.status(404).json({ msg: 'Task not found' });
}
if (task.userId.toString() !== req.user.id) {
return res.status(401).json({ msg: 'Not authorized' });
}
await task.remove();
res.json({ msg: 'Task deleted' });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
5. 前端 Vue 3 应用开发
5.1 项目初始化
首先,我们需要初始化 Vue 3 项目。打开终端,导航到 frontend
目录,然后运行以下命令:
bash
npm init vue@latest
按照提示完成项目初始化。安装完成后,进入项目目录并安装依赖:
javascript
cd frontend npm install
5.2 组件开发
5.2.1 登录组件
在 frontend/src/components/Login.vue
中创建登录组件:
javascript
<template>
<div class="login">
<h2>Login</h2>
<form @submit.prevent="handleLogin">
<div class="form-group">
<label for="email">Email</label>
<input type="email" v-model="email" required />
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" v-model="password" required />
</div>
<button type="submit">Login</button>
</form>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
email: '',
password: ''
};
},
methods: {
async handleLogin() {
try {
const response = await axios.post('http://localhost:3000/auth/login', {
email: this.email,
password: this.password
});
localStorage.setItem('token', response.data.token);
this.$router.push('/tasks');
} catch (error) {
alert('Login failed');
}
}
}
};
</script>
<style scoped>
/* 添加一些样式 */
</style>
5.2.2 注册组件
在 frontend/src/components/Register.vue
中创建注册组件:
javascript
<template>
<div class="register">
<h2>Register</h2>
<form @submit.prevent="handleRegister">
<div class="form-group">
<label for="username">Username</label>
<input type="text" v-model="username" required />
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" v-model="email" required />
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" v-model="password" required />
</div>
<button type="submit">Register</button>
</form>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
username: '',
email: '',
password: ''
};
},
methods: {
async handleRegister() {
try {
await axios.post('http://localhost:3000/auth/register', {
username: this.username,
email: this.email,
password: this.password
});
this.$router.push('/login');
} catch (error) {
alert('Registration failed');
}
}
}
};
</script>
<style scoped>
/* 添加一些样式 */
</style>
5.2.3 任务列表组件
在 frontend/src/components/TaskList.vue
中创建任务列表组件:
javascript
<template>
<div class="task-list">
<h2>Tasks</h2>
<ul>
<li v-for="task in tasks" :key="task._id">
<span>{{ task.title }}</span>
<button @click="deleteTask(task._id)">Delete</button>
</li>
</ul>
<form @submit.prevent="createTask">
<div class="form-group">
<label for="title">Title</label>
<input type="text" v-model="newTask.title" required />
</div>
<div class="form-group">
<label for="description">Description</label>
<input type="text" v-model="newTask.description" required />
</div>
<button type="submit">Create Task</button>
</form>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
tasks: [],
newTask: {
title: '',
description: ''
}
};
},
methods: {
async fetchTasks() {
const response = await axios.get('http://localhost:3000/tasks', {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
this.tasks = response.data;
},
async createTask() {
await axios.post('http://localhost:3000/tasks', this.newTask, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
this.newTask = { title: '', description: '' };
this.fetchTasks();
},
async deleteTask(id) {
await axios.delete(`http://localhost:3000/tasks/${id}`, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
this.fetchTasks();
}
},
created() {
this.fetchTasks();
}
};
</script>
<style scoped>
/* 添加一些样式 */
</style>
5.3 状态管理
为了更好地管理应用的状态,我们将使用 Vuex。首先,安装 Vuex:
bash
npm install vuex@next
在 frontend/src/store/index.js
中创建 Vuex 存储:
javascript
import { createStore } from 'vuex';
export default createStore({
state: {
token: localStorage.getItem('token') || null
},
mutations: {
setToken(state, token) {
state.token = token;
localStorage.setItem('token', token);
},
clearToken(state) {
state.token = null;
localStorage.removeItem('token');
}
},
actions: {
login({ commit }, token) {
commit('setToken', token);
},
logout({ commit }) {
commit('clearToken');
}
},
getters: {
isAuthenticated: state => !!state.token
}
});
6. 前后端联调
确保 MongoDB 和后端服务已启动。在 backend
目录下运行以下命令启动后端服务:
bash
npm start
在 frontend
目录下运行以下命令启动前端开发服务器:
bash
npm run serve
6.1 路由配置
为了更好地组织应用的路由,我们需要在 frontend/src/router/index.js
中配置路由:
javascript
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import Login from '../components/Login.vue';
import Register from '../components/Register.vue';
import TaskList from '../components/TaskList.vue';
import store from '../store';
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/login',
name: 'Login',
component: Login,
meta: { requiresAuth: false }
},
{
path: '/register',
name: 'Register',
component: Register,
meta: { requiresAuth: false }
},
{
path: '/tasks',
name: 'Tasks',
component: TaskList,
meta: { requiresAuth: true }
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.getters.isAuthenticated) {
next('/login');
} else {
next();
}
});
export default router;
6.2 主页组件
在 frontend/src/views/Home.vue
中创建主页组件:
javascript
<template>
<div class="home">
<h1>Welcome to the Task Manager</h1>
<p>Please <router-link to="/login">login</router-link> or <router-link to="/register">register</router-link> to get started.</p>
</div>
</template>
<script>
export default {
name: 'Home'
};
</script>
<style scoped>
/* 添加一些样式 */
</style>
6.3 登录和注册页面
在 frontend/src/App.vue
中设置默认路由:
javascript
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
};
</script>
<style>
/* 添加一些全局样式 */
</style>
7. 部署与测试
7.1 部署后端
将后端应用部署到云服务器或使用 Docker 容器化部署。这里以 Docker 为例,创建 Dockerfile
文件:
javascript
# 使用官方 Node.js 运行时镜像
FROM node:14
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json
COPY package*.json ./
# 安装依赖
RUN npm install
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动应用
CMD ["node", "src/server.js"]
构建并运行 Docker 容器:
bash
docker build -t task-manager-backend .
docker run -d -p 3000:3000 task-manager-backend
7.2 部署前端
将前端应用构建为生产版本,并部署到静态文件服务器。例如,使用 Nginx:
bash
npm run build
创建 nginx.conf
文件:
bash
server {
listen 80;
server_name your-domain.com;
location / {
root /path/to/dist;
try_files $uri $uri/ /index.html;
}
}
启动 Nginx 服务:
bash
sudo nginx -c /path/to/nginx.conf
7.3 测试
确保所有功能正常工作,包括用户注册、登录、任务创建、编辑和删除。可以使用 Postman 或浏览器进行测试。
8. 总结与展望
通过本文,我们成功构建了一个完整的 Node.js + MongoDB + Vue 3 全栈应用。这个应用实现了用户注册、登录、任务管理等功能。未来可以进一步扩展和优化,例如:
- 添加更多的用户权限管理
- 实现任务的分类和标签管理
- 增加实时通知功能
- 优化前端用户体验
希望本文对你有所帮助,祝你在全栈开发的道路上越走越远!